# revisions: 41707,41713,41714,41715,41719,41720,41732,41786,41787,41788,41792,41805,41806,41808,41809,41823,41824,41825,41840,41850,41851,41856,41862,41863,41864,41872,41876,41897,41931

git-svn-id: branches/fixes_3_2@41998 -
This commit is contained in:
marco 2019-05-05 15:31:55 +00:00
parent 0fa5c1b1e3
commit 7e85b53c0a
27 changed files with 1557 additions and 458 deletions

View File

@ -1,26 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="11"/>
<ProjectOptions BuildModesCount="1">
<Version Value="12"/>
<General>
<Flags>
<SaveOnlyProjectUnits Value="True"/>
<MainUnitHasCreateFormStatements Value="False"/>
<MainUnitHasTitleStatement Value="False"/>
<MainUnitHasScaledStatement Value="False"/>
<LRSInOutputDirectory Value="False"/>
<CompatibilityMode Value="True"/>
</Flags>
<SessionStorage Value="InProjectDir"/>
<MainUnit Value="0"/>
</General>
<BuildModes Count="1">
<BuildModes>
<Item1 Name="default" Default="True"/>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
</PublishOptions>
<RunParams>
<local>
<CommandLineParams Value="--suite=TTestJSONString.TestJSONStringToString"/>
<LaunchingApplication PathPlusParams="/usr/X11R6/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/>
</local>
<FormatVersion Value="2"/>
<Modes Count="1">
<Mode0 Name="default">

View File

@ -188,6 +188,7 @@ const
nAttributeIgnoredBecauseAbstractX = 3122;
nCreatingAnInstanceOfAbstractClassY = 3123;
nIllegalExpressionAfterX = 3124;
nMethodHidesNonVirtualMethodExactly = 3125;
// using same IDs as FPC
nVirtualMethodXHasLowerVisibility = 3250; // was 3050
@ -323,6 +324,7 @@ resourcestring
sAttributeIgnoredBecauseAbstractX = 'attribute ignored because abstract %s';
sCreatingAnInstanceOfAbstractClassY = 'Creating an instance of abstract class "%s"';
sIllegalExpressionAfterX = 'illegal expression after %s';
sMethodHidesNonVirtualMethodExactly = 'method hides identifier at "%s". Use reintroduce';
type
{ TResolveData - base class for data stored in TPasElement.CustomData }

View File

@ -2,7 +2,7 @@
This file is part of the Free Component Library
Pascal resolver
Copyright (c) 2018 Mattias Gaertner mattias@freepascal.org
Copyright (c) 2019 Mattias Gaertner mattias@freepascal.org
See the file COPYING.FPC, included in this distribution,
for details about the copyright.
@ -1443,7 +1443,7 @@ type
FindProcData: Pointer; var Abort: boolean); virtual;
function IsSameProcContext(ProcParentA, ProcParentB: TPasElement): boolean;
function FindProcSameSignature(const ProcName: string; Proc: TPasProcedure;
Scope: TPasScope): TPasProcedure;
Scope: TPasIdentifierScope; OnlyLocal: boolean): TPasProcedure;
protected
procedure SetCurrentParser(AValue: TPasParser); override;
procedure ScannerWarnDirective(Sender: TObject; Identifier: string;
@ -2024,7 +2024,7 @@ type
function GetFunctionType(El: TPasElement): TPasFunctionType;
function MethodIsStatic(El: TPasProcedure): boolean;
function IsMethod(El: TPasProcedure): boolean;
function IsHelperMethod(El: TPasElement): boolean;
function IsHelperMethod(El: TPasElement): boolean; virtual;
function IsHelper(El: TPasElement): boolean;
function IsExternalClass_Name(aClass: TPasClassType; const ExtName: string): boolean;
function IsProcedureType(const ResolvedEl: TPasResolverResult; HasValue: boolean): boolean;
@ -4820,6 +4820,13 @@ begin
else if (Proc is TPasConstructor)
and (Data^.Proc.ClassType=Proc.ClassType) then
// do not give a hint for hiding a constructor
else if Store then
begin
// method hides ancestor method with same signature
LogMsg(20190316152656,mtHint,
nMethodHidesNonVirtualMethodExactly,sMethodHidesNonVirtualMethodExactly,
[GetElementSourcePosStr(Proc)],Data^.Proc.ProcType);
end
else
begin
//writeln('TPasResolver.OnFindProc Proc=',Proc.PathName,' Data^.Proc=',Data^.Proc.PathName,' ',Proc.Visibility);
@ -4864,7 +4871,8 @@ begin
end;
function TPasResolver.FindProcSameSignature(const ProcName: string;
Proc: TPasProcedure; Scope: TPasScope): TPasProcedure;
Proc: TPasProcedure; Scope: TPasIdentifierScope; OnlyLocal: boolean
): TPasProcedure;
var
FindData: TFindProcData;
Abort: boolean;
@ -4874,7 +4882,10 @@ begin
FindData.Args:=Proc.ProcType.Args;
FindData.Kind:=fpkSameSignature;
Abort:=false;
Scope.IterateElements(ProcName,Scope,@OnFindProc,@FindData,Abort);
if OnlyLocal then
Scope.IterateLocalElements(ProcName,Scope,@OnFindProc,@FindData,Abort)
else
Scope.IterateElements(ProcName,Scope,@OnFindProc,@FindData,Abort);
Result:=FindData.Found;
end;
@ -5853,7 +5864,7 @@ var
DeclProc, Proc, ParentProc: TPasProcedure;
Abort, HasDots, IsClassConDestructor: boolean;
DeclProcScope, ProcScope: TPasProcedureScope;
ParentScope: TPasScope;
ParentScope: TPasIdentifierScope;
pm: TProcedureModifier;
ptm: TProcTypeModifier;
ObjKind: TPasObjKind;
@ -6093,13 +6104,15 @@ begin
if (ProcName<>'') and ProcNeedsBody(Proc) then
begin
// check if there is a forward declaration
ParentScope:=GetParentLocalScope;
//writeln('TPasResolver.FinishProcedureType ',GetObjName(TopScope),' ',GetObjName(Scopes[ScopeCount-2]));
ParentScope:=GetParentLocalScope as TPasIdentifierScope;
//writeln('TPasResolver.FinishProcedureType FindForward2 ParentScope=',GetObjName(ParentScope),'=',GetObjName(ParentScope.Element),' Proc=',GetObjName(Proc),' at ',GetElementSourcePosStr(Proc));
DeclProc:=FindProcSameSignature(ProcName,Proc,ParentScope);
DeclProc:=FindProcSameSignature(ProcName,Proc,ParentScope,true);
//writeln('TPasResolver.FinishProcedureType FindForward3 DeclProc=',GetObjName(DeclProc),' Proc.Parent=',GetObjName(Proc.Parent));
//if DeclProc<>nil then writeln('TPasResolver.FinishProcedureType DeclProc at ',GetElementSourcePosStr(DeclProc));
if (DeclProc=nil) and (Proc.Parent.ClassType=TImplementationSection) then
DeclProc:=FindProcSameSignature(ProcName,Proc,
(Proc.GetModule.InterfaceSection.CustomData) as TPasScope);
(Proc.GetModule.InterfaceSection.CustomData) as TPasIdentifierScope,true);
//writeln('TPasResolver.FinishProcedureType FindForward4 ',GetObjName(DeclProc),' at ',GetElementSourcePosStr(DeclProc));
if (DeclProc<>nil) then
begin
@ -6326,7 +6339,7 @@ begin
else if ImplProc.ClassType=TPasClassDestructor then
DeclProc:=ClassOrRecScope.ClassDestructor
else
DeclProc:=FindProcSameSignature(ProcName,ImplProc,ClassOrRecScope);
DeclProc:=FindProcSameSignature(ProcName,ImplProc,ClassOrRecScope,false);
if DeclProc=nil then
RaiseIdentifierNotFound(20170216151720,ImplProc.Name,ImplProc.ProcType);
DeclProcScope:=DeclProc.CustomData as TPasProcedureScope;
@ -8997,7 +9010,7 @@ begin
exit;
InhScope:=PushInheritedScope(TPasMembersType(AncestorScope.Element),true,nil);
end;
AncestorProc:=FindProcSameSignature(DeclProc.Name,DeclProc,InhScope);
AncestorProc:=FindProcSameSignature(DeclProc.Name,DeclProc,InhScope,false);
PopScope;
if AncestorProc=nil then
// 'inherited;' without ancestor DeclProc is silently ignored
@ -9689,7 +9702,8 @@ begin
if DeclEl is TPasProcedure then
begin
Proc:=TPasProcedure(DeclEl);
if (Access=rraAssign) and (Proc.ProcType is TPasFunctionType)
if (Access=rraAssign)
and (Proc.ProcType is TPasFunctionType)
and (Params.Parent.ClassType=TPasImplAssign)
and (TPasImplAssign(Params.Parent).left=Params) then
begin
@ -9705,6 +9719,7 @@ begin
end;
end;
end;
ComputeElement(NameExpr,ResolvedEl,[rcSetReferenceFlags]);
{$IFDEF VerbosePasResolver}
writeln('TPasResolver.ResolveArrayParamsExprName NameExp=',GetObjName(NameExpr),' ',GetResolverResultDbg(ResolvedEl));
@ -9715,11 +9730,33 @@ end;
procedure TPasResolver.ResolveArrayParamsArgs(Params: TParamsExpr;
const ResolvedValue: TPasResolverResult; Access: TResolvedRefAccess);
procedure ReadAccessParamValue;
var
Left: TPasExpr;
Ref: TResolvedReference;
begin
if Access=rraAssign then
begin
// ArrayStringPointer[]:=
// -> writing the element needs reading the value
Left:=Params.Value;
if (Left is TBinaryExpr) and (TBinaryExpr(Left).OpCode=eopSubIdent) then
Left:=TBinaryExpr(Left).right;
if Left.CustomData is TResolvedReference then
begin
Ref:=TResolvedReference(Left.CustomData);
if Ref.Access=rraAssign then
Ref.Access:=rraReadAndAssign;
end;
end;
end;
function CheckStringOrPointerIndex(IsStringIndex: boolean): boolean;
var
ArgExp: TPasExpr;
ResolvedArg: TPasResolverResult;
begin
ReadAccessParamValue;
if not IsStringIndex then
begin
// pointer
@ -9788,6 +9825,7 @@ begin
if ResolvedValue.IdentEl is TPasType then
RaiseMsg(20170216152215,nIllegalQualifierAfter,sIllegalQualifierAfter,
['[',ResolvedValue.IdentEl.ElementTypeName],Params);
ReadAccessParamValue;
CheckCallArrayCompatibility(TPasArrayType(TypeEl),Params,true,true);
for i:=0 to length(Params.Params)-1 do
AccessExpr(Params.Params[i],rraRead);
@ -10097,9 +10135,10 @@ begin
pekArrayParams:
begin
ComputeElement(Params.Value,ValueResolved,[]);
if IsDynArray(ValueResolved.LoTypeEl,false) then
// an element of a dynamic array is independent of the array variable
// an element of an open array depends on the argument
if IsDynArray(ValueResolved.LoTypeEl,false)
or (ValueResolved.BaseType=btPointer) then
// when accessing an element of a dynamic array the array is read
AccessExpr(Params.Value,rraRead)
else
AccessExpr(Params.Value,Access);
// Note: an element of an open or static array or a string is connected to the variable

View File

@ -410,6 +410,7 @@ type
Procedure TestProcOverloadBaseTypeOtherUnit;
Procedure TestProcOverloadBaseProcNoHint;
Procedure TestProcOverload_UnitOrderFail;
Procedure TestProcOverload_UnitSameSignature;
Procedure TestProcOverloadDelphiMissingNextOverload;
Procedure TestProcOverloadDelphiMissingPrevOverload;
Procedure TestProcOverloadDelphiUnit;
@ -639,6 +640,7 @@ type
// external class
Procedure TestExternalClass;
Procedure TestExternalClass_Descendant;
Procedure TestExternalClass_HintMethodHidesNonVirtualMethodExact;
// class of
Procedure TestClassOf;
@ -4649,7 +4651,6 @@ procedure TTestResolver.TestCAssignments;
begin
StartProgram(false);
Parser.Options:=Parser.Options+[po_cassignments];
Scanner.Options:=Scanner.Options+[po_cassignments];
Add('Type');
Add(' TFlag = (Flag1,Flag2);');
Add(' TFlags = set of TFlag;');
@ -4830,7 +4831,6 @@ procedure TTestResolver.TestAssign_Access;
begin
StartProgram(false);
Parser.Options:=Parser.Options+[po_cassignments];
Scanner.Options:=Scanner.Options+[po_cassignments];
Add('var i: longint;');
Add('begin');
Add(' {#a1_assign}i:={#a2_read}i;');
@ -6625,6 +6625,28 @@ begin
CheckResolverException(sIncompatibleTypeArgNo,nIncompatibleTypeArgNo);
end;
procedure TTestResolver.TestProcOverload_UnitSameSignature;
begin
AddModuleWithIntfImplSrc('unit1.pp',
LinesToStr([
'procedure Val(d: string);',
'']),
LinesToStr([
'procedure Val(d: string); begin end;',
'']));
StartProgram(true);
Add([
'uses unit1;',
'procedure Val(d: string);',
'begin',
'end;',
'var',
' s: string;',
'begin',
' Val(s);']);
ParseProgram;
end;
procedure TTestResolver.TestProcOverloadDelphiMissingNextOverload;
begin
StartProgram(false);
@ -11396,6 +11418,31 @@ begin
ParseProgram;
end;
procedure TTestResolver.TestExternalClass_HintMethodHidesNonVirtualMethodExact;
begin
StartProgram(false);
Add([
'{$modeswitch externalclass}',
'type',
' TJSObject = class external name ''JSObject''',
' procedure DoIt(p: pointer);',
' end;',
' TBird = class external name ''Bird''(TJSObject)',
' procedure DoIt(p: pointer);',
' end;',
'procedure TJSObject.DoIt(p: pointer);',
'begin',
' if p=nil then ;',
'end;',
'procedure TBird.DoIt(p: pointer); begin end;',
'var b: TBird;',
'begin',
' b.DoIt(nil);']);
ParseProgram;
CheckResolverHint(mtHint,nMethodHidesNonVirtualMethodExactly,
'method hides identifier at "afile.pp(5,19)". Use reintroduce');
end;
procedure TTestResolver.TestClassOf;
begin
StartProgram(false);
@ -12967,7 +13014,8 @@ begin
' end;',
'begin']);
ParseProgram;
CheckResolverHint(mtHint,nFunctionHidesIdentifier_NonVirtualMethod,'function hides identifier at "afile.pp(4,19)". Use overload or reintroduce');
CheckResolverHint(mtHint,nMethodHidesNonVirtualMethodExactly,
'method hides identifier at "afile.pp(4,19)". Use reintroduce');
end;
procedure TTestResolver.TestClassInterface_OverloadNoHint;
@ -14058,7 +14106,6 @@ end;
procedure TTestResolver.TestArray_DynArrayConstObjFPC;
begin
Parser.Options:=Parser.Options+[po_cassignments];
Scanner.Options:=Scanner.Options+[po_cassignments];
StartProgram(false);
Add([
'{$modeswitch arrayoperators}',

View File

@ -101,6 +101,8 @@ type
procedure TestM_Hint_ParameterNotUsedTypecast;
procedure TestM_Hint_OutParam_No_AssignedButNeverUsed;
procedure TestM_Hint_ArgPassed_No_ParameterNotUsed;
procedure TestM_Hint_ArrayArg_No_ParameterNotUsed;
procedure TestM_Hint_ArrayArg_No_ParameterNotUsed2;
procedure TestM_Hint_InheritedWithoutParams;
procedure TestM_Hint_LocalVariableNotUsed;
procedure TestM_HintsOff_LocalVariableNotUsed;
@ -1607,6 +1609,42 @@ begin
CheckUseAnalyzerUnexpectedHints;
end;
procedure TTestUseAnalyzer.TestM_Hint_ArrayArg_No_ParameterNotUsed;
begin
StartProgram(false);
Add([
'type TArr = array of boolean;',
'procedure Fly(a: TArr);',
'begin',
' a[1]:=true;',
'end;',
'begin',
' Fly(nil);',
'']);
AnalyzeProgram;
CheckUseAnalyzerUnexpectedHints;
end;
procedure TTestUseAnalyzer.TestM_Hint_ArrayArg_No_ParameterNotUsed2;
begin
StartProgram(false);
Add([
'type {#Tarr_used}TArr = array of boolean;',
'procedure {#Run_used}Run({#b_used}b: boolean);',
'begin',
' if b then ;',
'end;',
'procedure {#Fly_used}Fly({#a_used}a: TArr);',
'begin',
' Run(a[1]);',
'end;',
'begin',
' Fly(nil);',
'']);
AnalyzeProgram;
CheckUseAnalyzerUnexpectedHints;
end;
procedure TTestUseAnalyzer.TestM_Hint_InheritedWithoutParams;
begin
StartProgram(false);

View File

@ -717,7 +717,7 @@ begin
TStringArray(AP)[I]:=AValue.Strings[i];
end;
else
Raise ERESTAPI.CreateFmt('%s: unsupported array element type : ',[ClassName,GetEnumName(TypeInfo(TTypeKind),Ord(ET^.Kind))]);
Raise ERESTAPI.CreateFmt('%s: unsupported array element type : %s',[ClassName,GetEnumName(TypeInfo(TTypeKind),Ord(ET^.Kind))]);
end;
end;
end;
@ -1041,7 +1041,7 @@ begin
For I:=0 to Length(TStringArray(AP))-1 do
A.Add(TJSONString.Create(TStringArray(AP)[I]));
else
Raise ERESTAPI.CreateFmt('%s: unsupported array element type : ',[ClassName,GetEnumName(TypeInfo(TTypeKind),Ord(ET^.Kind))]);
Raise ERESTAPI.CreateFmt('%s: unsupported array element type : %s',[ClassName,GetEnumName(TypeInfo(TTypeKind),Ord(ET^.Kind))]);
end;
end;

View File

@ -65,6 +65,7 @@ Type
Constructor Create(AOwner :TComponent); override;
Destructor Destroy; override;
class function ExtractUserNamePassword(Req: TRequest; out UN, PW: UTF8String): Boolean;
class function ExtractUserName(Req: TRequest) : UTF8String;
Function NeedConnection : Boolean; override;
function DoAuthenticateRequest(IO : TRestIO) : Boolean; override;
Published
@ -133,13 +134,14 @@ begin
Result:=HaveAuthSQL and (AuthConnection=Nil);
end;
Function TRestBasicAuthenticator.HaveAuthSQL : Boolean;
function TRestBasicAuthenticator.HaveAuthSQL: Boolean;
begin
Result:=(FAuthSQL.Count>0) and (Trim(FAuthSQL.Text)<>'');
end;
function TRestBasicAuthenticator.AuthenticateUserUsingSQl(IO : TRestIO; Const UN,PW : UTF8String; Out UID : UTF8String) : Boolean;
function TRestBasicAuthenticator.AuthenticateUserUsingSQl(IO: TRestIO;
const UN, PW: UTF8String; out UID: UTF8String): Boolean;
Var
Conn : TSQLConnection;
@ -179,7 +181,8 @@ begin
end;
end;
Class Function TRestBasicAuthenticator.ExtractUserNamePassword(Req : TRequest; Out UN,PW : UTF8String) : Boolean;
class function TRestBasicAuthenticator.ExtractUserNamePassword(Req: TRequest;
out UN, PW: UTF8String): Boolean;
Var
S,A : String;
@ -204,7 +207,17 @@ begin
end;
end;
function TRestBasicAuthenticator.DoAuthenticateRequest(io: TRestIO): Boolean;
class function TRestBasicAuthenticator.ExtractUserName(Req: TRequest): UTF8String;
Var
PW : UTF8String;
begin
if not ExtractUserNamePassword(Req,Result,PW) then
Result:='?';
end;
function TRestBasicAuthenticator.DoAuthenticateRequest(IO: TRestIO): Boolean;
Var
UID,UN,PW : UTF8String;

View File

@ -19,14 +19,34 @@ unit sqldbrestbridge;
interface
uses
Classes, SysUtils, DB, SQLDB, httpdefs, httproute, fpjson, sqldbrestschema, sqldbrestio, sqldbrestdata, sqldbrestauth;
Classes, SysUtils, DB, SqlTypes, SQLDB, httpdefs, httproute, fpjson, sqldbrestschema, sqldbrestio, sqldbrestdata, sqldbrestauth;
Type
TRestDispatcherOption = (rdoConnectionInURL,rdoExposeMetadata,rdoCustomView,rdoHandleCORS,rdoAccessCheckNeedsDB);
TRestDispatcherOption = (rdoConnectionInURL, // Route includes connection :Connection/:Resource[/:ID]
rdoExposeMetadata, // expose metadata resource /metadata[/:Resource]
rdoCustomView, // Expose custom view /customview
rdoHandleCORS, // Handle CORS requests
rdoAccessCheckNeedsDB, // Authenticate after connection to database was made.
rdoConnectionResource // Enable connection managament through /_connection[/:Conn] resource
// rdoServerInfo // Enable querying server info through /_serverinfo resource
);
TRestDispatcherOptions = set of TRestDispatcherOption;
TRestDispatcherLogOption = (rloUser, // Include username in log messages, when available
rtloHTTP, // Log HTTP request (remote, URL)
rloResource, // Log resource requests (operation, resource)
rloConnection, // Log database connections (connect to database)
rloAuthentication, // Log authentication attempt
rloSQL, // Log SQL statements. (not on user-supplied connection)
rloResultStatus // Log result status.
);
TRestDispatcherLogOptions = Set of TRestDispatcherLogOption;
Const
DefaultDispatcherOptions = [rdoExposeMetadata];
AllDispatcherLogOptions = [Low(TRestDispatcherLogOption)..High(TRestDispatcherLogOption)];
DefaultDispatcherLogOptions = AllDispatcherLogOptions-[rloSQL];
DefaultLogSQLOptions = LogAllEvents;
Type
@ -45,6 +65,7 @@ Type
FPassword: UTF8String;
FPort: Word;
FRole: UTF8String;
FSchemaName: UTF8String;
FUserName: UTF8String;
FNotifier : TComponent;
function GetName: UTF8String;
@ -52,6 +73,8 @@ Type
procedure SetParams(AValue: TStrings);
Protected
Function GetDisplayName: string; override;
// For use in the REST Connection resource
Property SchemaName : UTF8String Read FSchemaName Write FSchemaName;
Public
constructor Create(ACollection: TCollection); override;
Destructor Destroy; override;
@ -92,9 +115,9 @@ Type
procedure SetConn(aIndex : integer; AValue: TSQLDBRestConnection);
Public
// Index of connection by name (case insensitive)
Function IndexOfConnection(const aName : string) : Integer;
Function IndexOfConnection(const aName : UTF8string) : Integer;
// Find connection by name (case insensitive), nil if none found
Function FindConnection(const aName : string) : TSQLDBRestConnection;
Function FindConnection(const aName : UTF8string) : TSQLDBRestConnection;
// Add new instance, setting basic properties. Return new instance
Function AddConnection(Const AType,aHostName,aDatabaseName,aUserName,aPassword : UTF8String) : TSQLDBRestConnection;
// Save connection definitions to JSON file.
@ -142,6 +165,7 @@ Type
procedure SetSchema(aIndex : Integer; AValue: TSQLDBRestSchemaRef);
Public
Function AddSchema (aSchema : TSQLDBRestSchema) : TSQLDBRestSchemaRef;
Function IndexOfSchema(aSchemaName : String) : Integer;
Property Schemas[aIndex :Integer] : TSQLDBRestSchemaRef Read GetSchema Write SetSchema;default;
end;
@ -155,20 +179,25 @@ Type
TRestExceptionEvent = Procedure(Sender : TObject; aRequest : TRequest; Const AResource : string; E : Exception) of object;
TRestOperationEvent = Procedure(Sender : TObject; aConn: TSQLConnection; aResource : TSQLDBRestResource) of object;
TRestGetFormatEvent = Procedure(Sender : TObject; aRest : TRequest; var aFormat : String) of object;
TRestLogEvent = Procedure(Sender : TObject; aType : TRestDispatcherLogOption; Const aMessage : UTF8String) of object;
TSQLDBRestDispatcher = Class(TComponent)
Private
Class Var FIOClass : TRestIOClass;
Class Var FDBHandlerClass : TSQLDBRestDBHandlerClass;
private
FAdminUserIDs: TStrings;
FCORSAllowCredentials: Boolean;
FCORSAllowedOrigins: String;
FCORSMaxAge: Integer;
FDBLogOptions: TDBEventTypes;
FDispatchOptions: TRestDispatcherOptions;
FInputFormat: String;
FCustomViewResource : TSQLDBRestResource;
FLogOptions: TRestDispatcherLogOptions;
FMetadataResource : TSQLDBRestResource;
FMetadataDetailResource : TSQLDBRestResource;
FConnectionResource : TSQLDBRestResource;
FActive: Boolean;
FAfterDelete: TRestOperationEvent;
FAfterGet: TRestOperationEvent;
@ -190,21 +219,35 @@ Type
FOnGetConnectionName: TGetConnectionNameEvent;
FOnGetInputFormat: TRestGetFormatEvent;
FOnGetOutputFormat: TRestGetFormatEvent;
FOnLog: TRestLogEvent;
FOutputFormat: String;
FOutputOptions: TRestOutputoptions;
FSchemas: TSQLDBRestSchemaList;
FListRoute: THTTPRoute;
FItemRoute: THTTPRoute;
FConnectionsRoute: THTTPRoute;
FConnectionItemRoute: THTTPRoute;
FMetadataRoute: THTTPRoute;
FMetadataItemRoute: THTTPRoute;
FStatus: TRestStatusConfig;
FStrings: TRestStringsConfig;
procedure SetActive(AValue: Boolean);
procedure SetAdminUserIDS(AValue: TStrings);
procedure SetAuthenticator(AValue: TRestAuthenticator);
procedure SetConnections(AValue: TSQLDBRestConnectionList);
procedure SetDispatchOptions(AValue: TRestDispatcherOptions);
procedure SetSchemas(AValue: TSQLDBRestSchemaList);
procedure SetStatus(AValue: TRestStatusConfig);
procedure SetStrings(AValue: TRestStringsConfig);
Protected
// Logging
Function MustLog(aLog : TRestDispatcherLogOption) : Boolean; inline;
procedure DoSQLLog(Sender: TObject; EventType: TDBEventType; const Msg: String); virtual;
procedure DoLog(aLog: TRestDispatcherLogOption; IO : TRestIO; const aMessage: UTF8String); virtual;
procedure DoLog(aLog: TRestDispatcherLogOption; IO : TRestIO; const Fmt: UTF8String;
Args: array of const);
// Auxiliary methods.
Procedure Loaded; override;
Procedure Notification(AComponent: TComponent; Operation: TOperation); override;
function FindConnection(IO: TRestIO): TSQLDBRestConnection;
// Factory methods. Override these to customize various helper classes.
@ -222,6 +265,13 @@ Type
function GetConnectionName(IO: TRestIO): UTF8String;
function GetSQLConnection(aConnection: TSQLDBRestConnection; Out aTransaction : TSQLTransaction): TSQLConnection; virtual;
procedure DoneSQLConnection(aConnection: TSQLDBRestConnection; AConn: TSQLConnection; aTransaction : TSQLTransaction); virtual;
// Connections dataset API
procedure ConnectionsToDataset(D: TDataset); virtual;
procedure DoConnectionDelete(DataSet: TDataSet); virtual;
procedure DoConnectionPost(DataSet: TDataSet);virtual;
procedure DatasetToConnection(D: TDataset; C: TSQLDBRestConnection); virtual;
procedure ConnectionToDataset(C: TSQLDBRestConnection; D: TDataset); virtual;
procedure DoConnectionResourceAllowed(aSender: TObject; aContext: TBaseRestContext; var allowResource: Boolean);
// Error handling
procedure CreateErrorContent(IO: TRestIO; aCode: Integer; AExtraMessage: UTF8String); virtual;
procedure HandleException(E: Exception; IO: TRestIO); virtual;
@ -245,8 +295,10 @@ Type
// Special resources for Metadata handling
function CreateMetadataDataset(IO: TRestIO; AOwner: TComponent): TDataset; virtual;
function CreateMetadataDetailDataset(IO: TRestIO; Const aResourceName : String; AOwner: TComponent): TDataset; virtual;
function CreateConnectionDataset(IO: TRestIO; AOwner: TComponent): TDataset; virtual;
function CreateMetadataDetailResource: TSQLDBRestResource; virtual;
function CreateMetadataResource: TSQLDBRestResource; virtual;
Function CreateConnectionResource : TSQLDBRestResource; virtual;
// Custom view handling
function CreateCustomViewResource: TSQLDBRestResource; virtual;
function CreateCustomViewDataset(IO: TRestIO; const aSQL: String; AOwner: TComponent): TDataset;
@ -266,6 +318,8 @@ Type
Destructor Destroy; override;
procedure RegisterRoutes;
procedure UnRegisterRoutes;
procedure HandleMetadataRequest(aRequest : TRequest; aResponse : TResponse);
procedure HandleConnRequest(aRequest : TRequest; aResponse : TResponse);
procedure HandleRequest(aRequest : TRequest; aResponse : TResponse);
Function ExposeDatabase(Const aType,aHostName,aDatabaseName,aUserName,aPassword : String; aTables : Array of String; aMinFieldOpts : TRestFieldOptions = []) : TSQLDBRestConnection;
Function ExposeDatabase(Const aType,aHostName,aDatabaseName,aUserName,aPassword : String; aTables : TStrings = nil; aMinFieldOpts : TRestFieldOptions = []) : TSQLDBRestConnection;
@ -281,6 +335,8 @@ Type
// Base URL
property BasePath : UTF8String Read FBaseURL Write FBaseURL;
// Default connection to use if none is detected from request/schema
// This connection will also be used to authenticate the user for connection API,
// so it must be set if you use SQL to authenticate the user.
Property DefaultConnection : UTF8String Read FDefaultConnection Write FDefaultConnection;
// Input/Output strings configuration
Property Strings : TRestStringsConfig Read FStrings Write SetStrings;
@ -293,7 +349,7 @@ Type
// Set this to allow only this output format.
Property OutputFormat : String Read FOutputFormat Write FOutputFormat;
// Dispatcher options
Property DispatchOptions : TRestDispatcherOptions Read FDispatchOptions Write FDispatchOptions default DefaultDispatcherOptions;
Property DispatchOptions : TRestDispatcherOptions Read FDispatchOptions Write SetDispatchOptions default DefaultDispatcherOptions;
// Authenticator for requests
Property Authenticator : TRestAuthenticator Read FAuthenticator Write SetAuthenticator;
// If >0, Enforce a limit on output results.
@ -304,6 +360,12 @@ Type
Property CORSMaxAge : Integer Read FCORSMaxAge Write FCORSMaxAge;
// Access-Control-Allow-Credentials header value. Set to zero not to send the header
Property CORSAllowCredentials : Boolean Read FCORSAllowCredentials Write FCORSAllowCredentials;
// UserIDs of the user(s) that are allowed to see and modify the connection resource.
Property AdminUserIDs : TStrings Read FAdminUserIDs Write SetAdminUserIDS;
// Logging options
Property LogOptions : TRestDispatcherLogOptions Read FLogOptions write FLogOptions default DefaultDispatcherLogOptions;
// SQL Log options. Only for connections managed by RestDispatcher
Property LogSQLOptions : TDBEventTypes Read FDBLogOptions write FDBLogOptions default DefaultLogSQLOptions;
// Called when Basic authentication is sufficient.
Property OnBasicAuthentication : TBasicAuthenticationEvent Read FOnBasicAuthentication Write FOnBasicAuthentication;
// Allow a particular resource or not.
@ -334,9 +396,14 @@ Type
Property BeforeDelete : TRestOperationEvent Read FBeforeDelete Write FBeforeDelete;
// Called After a DELETE request.
Property AfterDelete : TRestOperationEvent Read FAfterDelete Write FAfterDelete;
// Called when logging
Property OnLog : TRestLogEvent Read FOnLog Write FOnLog;
end;
Const
LogNames : Array[TRestDispatcherLogOption] of string = (
'User','HTTP','Resource','Connection','Authentication','SQL','Result'
);
implementation
@ -406,6 +473,13 @@ begin
Result.Enabled:=True;
end;
function TSQLDBRestSchemaList.IndexOfSchema(aSchemaName: String): Integer;
begin
Result:=Count-1;
While (Result>=0) and Not (Assigned(GetSchema(Result).Schema) and SameText(GetSchema(Result).Schema.Name,aSchemaName)) do
Dec(Result);
end;
{ TSQLDBRestDispatcher }
procedure TSQLDBRestDispatcher.SetConnections(AValue: TSQLDBRestConnectionList);
@ -414,15 +488,40 @@ begin
FConnections.Assign(AValue);
end;
procedure TSQLDBRestDispatcher.SetDispatchOptions(AValue: TRestDispatcherOptions);
begin
if (rdoConnectionResource in aValue) then
Include(aValue,rdoConnectionInURL);
if FDispatchOptions=AValue then Exit;
FDispatchOptions:=AValue;
end;
procedure TSQLDBRestDispatcher.DoConnectionResourceAllowed(aSender: TObject;
aContext: TBaseRestContext; var allowResource: Boolean);
begin
AllowResource:=(AdminUserIDs.Count=0) or (AdminUserIDs.IndexOf(aContext.UserID)<>-1);
end;
procedure TSQLDBRestDispatcher.SetActive(AValue: Boolean);
begin
if FActive=AValue then Exit;
if AValue then
DoRegisterRoutes
else
UnRegisterRoutes;
if FActive=AValue then
Exit;
if Not (csLoading in ComponentState) then
begin
if AValue then
DoRegisterRoutes
else
UnRegisterRoutes;
end;
FActive:=AValue;
end;
procedure TSQLDBRestDispatcher.SetAdminUserIDS(AValue: TStrings);
begin
if FAdminUserIDs=AValue then Exit;
FAdminUserIDs.Assign(AValue);
end;
procedure TSQLDBRestDispatcher.SetAuthenticator(AValue: TRestAuthenticator);
@ -453,18 +552,133 @@ begin
FStrings.Assign(AValue);
end;
function TSQLDBRestDispatcher.MustLog(aLog: TRestDispatcherLogOption): Boolean;
begin
Result:=aLog in FLogOptions;
end;
procedure TSQLDBRestDispatcher.DoSQLLog(Sender: TObject; EventType: TDBEventType; const Msg: String);
Const
EventNames : Array [TDBEventType] of string =
('Custom','Prepare', 'Execute', 'Fetch', 'Commit', 'RollBack', 'ParamValue', 'ActualSQL');
Var
aMsg : UTF8String;
begin
if not MustLog(rloSQl) then // avoid string ops
exit;
aMsg:=EventNames[EventType]+': '+Msg;
if Sender is TRestIO then
DoLog(rloSQL,TRestIO(Sender),aMsg)
else
DoLog(rloSQL,Nil,aMsg)
end;
procedure TSQLDBRestDispatcher.DoLog(aLog: TRestDispatcherLogOption; IO: TRestIO; const aMessage: UTF8String);
Var
aMsg : UTF8String;
begin
aMsg:='';
if MustLog(aLog) and Assigned(FOnLog) then
begin
if MustLog(rloUser) and Assigned(IO) then
begin
if IO.UserID='' then
aMsg:='(User: ?) '
else
aMsg:=Format('(User: %s) ',[IO.UserID]);
end;
aMsg:=aMsg+aMessage;
FOnLog(Self,aLog,aMsg);
end;
end;
procedure TSQLDBRestDispatcher.DoLog(aLog: TRestDispatcherLogOption;IO: TRestIO;
const Fmt: UTF8String; Args: array of const);
Var
S : UTF8string;
begin
if not MustLog(aLog) then exit; // avoid expensive format
try
S:=Format(fmt,Args); // Encode ?
except
on E : exception do
S:=Format('Error "%s" formatting "%s" with %d arguments: %s',[E.ClassName,Fmt,Length(Args),E.Message])
end;
DoLog(aLog,IO,S);
end;
procedure TSQLDBRestDispatcher.Loaded;
begin
inherited Loaded;
if FActive then
RegisterRoutes;
end;
procedure TSQLDBRestDispatcher.HandleConnRequest(aRequest : TRequest; aResponse : TResponse);
begin
aRequest.RouteParams['resource']:=Strings.ConnectionResourceName;
HandleRequest(aRequest,aResponse);
end;
procedure TSQLDBRestDispatcher.HandleMetadataRequest(aRequest: TRequest;aResponse: TResponse);
Var
LogMsg,UN : UTF8String;
begin
if MustLog(rtloHTTP) then
begin
LogMsg:='';
With aRequest do
begin
UN:=RemoteHost;
if (UN='') then
UN:=RemoteAddr;
if (UN<>'') then
LogMsg:='From: '+UN+'; ';
LogMsg:=LogMsg+'URL: '+URL;
end;
UN:=TRestBasicAuthenticator.ExtractUserName(aRequest);
if (UN<>'?') then
LogMsg:='User: '+UN+LogMsg;
DoLog(rtloHTTP,Nil,LogMsg);
end;
aRequest.RouteParams['resource']:=Strings.MetadataResourceName;
HandleRequest(aRequest,aResponse);
end;
procedure TSQLDBRestDispatcher.DoRegisterRoutes;
Var
Res : String;
Res,C : UTF8String;
begin
Res:=IncludeHTTPPathDelimiter(BasePath);
if rdoConnectionInURL in DispatchOptions then
if (rdoConnectionResource in DispatchOptions) then
begin
C:=Strings.GetRestString(rpConnectionResourceName);
FConnectionsRoute:=HTTPRouter.RegisterRoute(res+C,@HandleConnRequest);
FConnectionItemRoute:=HTTPRouter.RegisterRoute(res+C+'/:id',@HandleConnRequest);
end;
if (rdoConnectionInURL in DispatchOptions) then
begin
C:=Strings.GetRestString(rpMetadataResourceName);
FMetadataRoute:=HTTPRouter.RegisterRoute(res+C,@HandleMetaDataRequest);
FMetadataItemRoute:=HTTPRouter.RegisterRoute(res+C+'/:id',@HandleMetaDataRequest);
Res:=Res+':connection/';
end;
Res:=Res+':resource';
FListRoute:=HTTPRouter.RegisterRoute(res,@HandleRequest);
FItemRoute:=HTTPRouter.RegisterRoute(Res+'/:id',@HandleRequest);
end;
function TSQLDBRestDispatcher.GetInputFormat(IO : TRestIO) : String;
@ -630,17 +844,22 @@ begin
FSchemas:=CreateSchemaList;
FOutputOptions:=allOutputOptions;
FDispatchOptions:=DefaultDispatcherOptions;
FLogOptions:=DefaultDispatcherLogOptions;
FDBLogOptions:=DefaultLogSQLOptions;
FStatus:=CreateRestStatusConfig;
FCORSMaxAge:=SecsPerDay;
FCORSAllowCredentials:=True;
FAdminUserIDs:=TStringList.Create;
end;
destructor TSQLDBRestDispatcher.Destroy;
begin
Authenticator:=Nil;
FreeAndNil(FAdminUserIDs);
FreeAndNil(FCustomViewResource);
FreeAndNil(FMetadataResource);
FreeAndNil(FMetadataDetailResource);
FreeAndNil(FConnectionResource);
FreeAndNil(FSchemas);
FreeAndNil(FConnections);
FreeAndNil(FStrings);
@ -681,7 +900,10 @@ function TSQLDBRestDispatcher.CreateCustomViewResource: TSQLDBRestResource;
begin
Result:=TCustomViewResource.Create(Nil);
Result.ResourceName:=FStrings.GetRestString(rpCustomViewResourceName);
Result.AllowedOperations:=[roGet];
if rdoHandleCORS in DispatchOptions then
Result.AllowedOperations:=[roGet,roOptions,roHead]
else
Result.AllowedOperations:=[roGet,roHead];
end;
function TSQLDBRestDispatcher.CreateMetadataResource: TSQLDBRestResource;
@ -692,13 +914,13 @@ Var
begin
Result:=TSQLDBRestResource.Create(Nil);
Result.ResourceName:='metaData';
Result.ResourceName:=Strings.GetRestString(rpMetadataResourceName);
if rdoHandleCORS in DispatchOptions then
Result.AllowedOperations:=[roGet,roOptions,roHead]
else
Result.AllowedOperations:=[roGet,roHead];
Result.Fields.AddField('name',rftString,[foRequired]);
Result.Fields.AddField('schemaName',rftString,[foRequired]);
Result.Fields.AddField('name',rftString,[foRequired]).MaxLen:=255;
Result.Fields.AddField('schemaName',rftString,[foRequired]).MaxLen:=255;
for O in TRestOperation do
if O<>roUnknown then
begin
@ -708,6 +930,32 @@ begin
end;
end;
function TSQLDBRestDispatcher.CreateConnectionResource: TSQLDBRestResource;
Var
Def : TRestFieldOptions;
begin
Def:=[foInInsert,foInUpdate,foFilter];
Result:=TSQLDBRestResource.Create(Nil);
Result.ResourceName:=Strings.GetRestString(rpConnectionResourceName);
Result.AllowedOperations:=[roGet,roPut,roPost,roDelete];
if rdoHandleCORS in DispatchOptions then
Result.AllowedOperations:=Result.AllowedOperations+[roOptions,roHead];
Result.Fields.AddField('name',rftString,Def+[foInKey,foRequired]);
Result.Fields.AddField('dbType',rftString,Def+[foRequired]);
Result.Fields.AddField('dbName',rftString,Def+[foRequired]);
Result.Fields.AddField('dbHostName',rftString,Def);
Result.Fields.AddField('dbUserName',rftString,Def);
Result.Fields.AddField('dbPassword',rftString,Def);
Result.Fields.AddField('dbCharSet',rftString,Def);
Result.Fields.AddField('dbRole',rftString,Def);
Result.Fields.AddField('dbPort',rftInteger,Def);
Result.Fields.AddField('enabled',rftBoolean,Def);
Result.Fields.AddField('expose',rftBoolean,Def);
Result.Fields.AddField('exposeSchemaName',rftString,Def);
Result.OnResourceAllowed:=@DoConnectionResourceAllowed;
end;
function TSQLDBRestDispatcher.CreateMetadataDetailResource: TSQLDBRestResource;
Var
@ -721,10 +969,10 @@ begin
Result.AllowedOperations:=[roGet,roOptions,roHead]
else
Result.AllowedOperations:=[roGet,roHead];
Result.Fields.AddField('name',rftString,[]);
Result.Fields.AddField('type',rftString,[]);
Result.Fields.AddField('name',rftString,[]).MaxLen:=255;
Result.Fields.AddField('type',rftString,[]).MaxLen:=20;
Result.Fields.AddField('maxlen',rftInteger,[]);
Result.Fields.AddField('format',rftString,[]);
Result.Fields.AddField('format',rftString,[]).MaxLen:=50;
for O in TRestFieldOption do
begin
Str(O,S);
@ -741,6 +989,7 @@ function TSQLDBRestDispatcher.FindSpecialResource(IO : TRestIO; aResource: UTF8S
Result:=(rdoCustomView in DispatchOptions)
and SameText(aResource,Strings.GetRestString(rpCustomViewResourceName));
end;
Function IsMetadata : Boolean;inline;
begin
@ -748,6 +997,13 @@ function TSQLDBRestDispatcher.FindSpecialResource(IO : TRestIO; aResource: UTF8S
and SameText(aResource,Strings.GetRestString(rpMetaDataResourceName));
end;
Function IsConnection : Boolean;inline;
begin
Result:=(rdoConnectionResource in DispatchOptions)
and SameText(aResource,Strings.GetRestString(rpConnectionResourceName));
end;
Var
N : UTF8String;
@ -759,6 +1015,12 @@ begin
FCustomViewResource:=CreateCustomViewResource;
Result:=FCustomViewResource;
end
else if IsConnection then
begin
if FConnectionResource=Nil then
FConnectionResource:=CreateConnectionResource;
Result:=FConnectionResource;
end
else If isMetadata then
if (IO.GetVariable('ID',N,[vsRoute,vsQuery])=vsNone) then
begin
@ -775,7 +1037,6 @@ begin
Result:=FMetadataDetailResource;
end;
end
end;
function TSQLDBRestDispatcher.FindRestResource(aResource: UTF8String): TSQLDBRestResource;
@ -872,6 +1133,10 @@ function TSQLDBRestDispatcher.GetSQLConnection(
): TSQLConnection;
begin
Result:=Nil;
aTransaction:=Nil;
if aConnection=Nil then
exit;
Result:=aConnection.SingleConnection;
if (Result=Nil) then
begin
@ -973,6 +1238,7 @@ begin
if not Result then exit;
Result:=(aResource=FMetadataResource) or
(aResource=FMetadataDetailResource) or
(aResource=FConnectionResource) or
(aResource=FCustomViewResource);
end;
@ -1124,6 +1390,165 @@ begin
end;
end;
procedure TSQLDBRestDispatcher.DatasetToConnection(D: TDataset; C : TSQLDBRestConnection);
begin
C.Name:=UTF8Encode(D.FieldByName('name').AsWideString);
C.ConnectionType:=D.FieldByName('dbType').AsString;
C.DatabaseName:=UTF8Encode(D.FieldByName('dbName').AsWideString);
C.HostName:=D.FieldByName('dbHostName').AsString;
C.UserName:=UTF8Encode(D.FieldByName('dbUserName').AsWideString);
C.Password:=UTF8Encode(D.FieldByName('dbPassword').AsWideString);
C.CharSet:=D.FieldByName('dbCharSet').AsString;
C.Role:=D.FieldByName('dbRole').AsString;
C.Port:=D.FieldByName('dbPort').AsInteger;
C.Enabled:=D.FieldByName('enabled').AsBoolean;
if D.FieldByName('expose').AsBoolean then
C.SchemaName:=D.FieldByName('exposeSchemaName').AsString;
end;
procedure TSQLDBRestDispatcher.ConnectionToDataset(C : TSQLDBRestConnection;D: TDataset);
begin
D.FieldByName('key').AsWideString:=UTF8Decode(C.Name);
D.FieldByName('name').AsWideString:=UTF8Decode(C.Name);
D.FieldByName('dbType').AsString:=C.ConnectionType;
D.FieldByName('dbName').AsWideString:=UTF8Decode(C.DatabaseName);
D.FieldByName('dbHostName').AsString:=C.HostName;
D.FieldByName('dbUserName').AsWideString:=UTF8Decode(C.UserName);
D.FieldByName('dbPassword').AsWideString:=UTF8Decode(C.Password);
D.FieldByName('dbCharSet').AsString:=C.CharSet;
D.FieldByName('dbRole').AsString:=C.Role;
D.FieldByName('dbPort').AsInteger:=C.Port;
D.FieldByName('enabled').AsBoolean:=C.Enabled;
D.FieldByName('expose').AsBoolean:=(C.SchemaName<>'');
D.FieldByName('exposeSchemaName').AsString:=C.SchemaName;
end;
procedure TSQLDBRestDispatcher.ConnectionsToDataset(D: TDataset);
Var
C : TSQLDBRestConnection;
I : Integer;
begin
For I:=0 to Connections.Count-1 do
begin
C:=Connections[i];
D.Append;
ConnectionToDataset(C,D);
D.Post;
end;
end;
procedure TSQLDBRestDispatcher.DoConnectionDelete(DataSet: TDataSet);
Var
I,J : Integer;
C : TSQLDBRestConnection;
begin
I:=Connections.IndexOfConnection(UTF8Encode(Dataset.FieldByName('name').AsWideString));
if I<>-1 then
begin
C:=Connections[i];
if C.SingleConnection<>Nil then
DoneSQLConnection(C,C.SingleConnection,Nil);
if C.SchemaName<>'' then
begin
J:=Schemas.IndexOfSchema(C.SchemaName);
if J<>-1 then
begin
Schemas[J].Schema.Free;
Schemas[J].Schema:=Nil;
end;
Schemas.Delete(J);
end;
Connections.Delete(I);
end
else
Raise ESQLDBRest.Create(404,'NOT FOUND');
end;
procedure TSQLDBRestDispatcher.DoConnectionPost(DataSet: TDataSet);
Var
isNew : Boolean;
C : TSQLDBRestConnection;
N : UTF8String;
UN : UnicodeString;
S : TSQLDBRestSchema;
begin
IsNew:=Dataset.State=dsInsert;
if IsNew then
C:=Connections.Add as TSQLDBRestConnection
else
begin
UN:=UTF8Decode(Dataset.FieldByName('key').AsString);
// C:=Connections[Dataset.RecNo-1];
C:=Connections.FindConnection(Utf8Encode(UN));
if (C=Nil) then
Raise ESQLDBRest.Create(404,'NOT FOUND');
end;
if Assigned(C.SingleConnection) then
DoneSQLConnection(C,C.SingleConnection,Nil);
DatasetToConnection(Dataset,C);
if (Dataset.FieldByName('expose').AsBoolean) and isNew then
begin
N:=C.SchemaName;
if N='' then
N:=C.Name+'schema';
if (Schemas.IndexOfSchema(N)<>-1) then
Raise ESQLDBRest.Create(400,'DUPLICATE SCHEMA');
try
S:=ExposeConnection(C,Nil);
except
if IsNew then
C.Free;
Raise;
end;
S.Name:=N;
end;
end;
function TSQLDBRestDispatcher.CreateConnectionDataset(IO: TRestIO; AOwner: TComponent): TDataset;
Var
BD : TRestBufDataset;
begin
if IO=Nil then exit;
BD:=TRestBufDataset.Create(aOwner);
try
Result:=BD;
// Key field is not exposed
Result.FieldDefs.add('key',ftWidestring,255);
Result.FieldDefs.add('name',ftWidestring,255);
Result.FieldDefs.add('dbType',ftString,20);
Result.FieldDefs.add('dbName',ftWideString,255);
Result.FieldDefs.add('dbHostName',ftString,255);
Result.FieldDefs.add('dbUserName',ftWideString,255);
Result.FieldDefs.add('dbPassword',ftWideString,255);
Result.FieldDefs.add('dbCharSet',ftString,50);
Result.FieldDefs.add('dbRole',ftString,255);
Result.FieldDefs.add('dbPort',ftInteger,0);
Result.FieldDefs.add('enabled',ftBoolean,0);
Result.FieldDefs.add('expose',ftBoolean,0);
Result.FieldDefs.add('exposeSchemaName',ftWideString,255);
BD.CreateDataset;
ConnectionsToDataset(BD);
BD.IndexDefs.Add('uName','name',[ixUnique]);
BD.IndexName:='uName';
BD.First;
BD.BeforePost:=@DoConnectionPost;
BD.BeforeDelete:=@DoConnectionDelete;
except
BD.Free;
Raise;
end;
end;
function TSQLDBRestDispatcher.CreateCustomViewDataset(IO: TRestIO;
const aSQL: String; AOwner: TComponent): TDataset;
@ -1159,6 +1584,8 @@ begin
Result:=Nil;
if (IO.Resource=FMetadataResource) then
Result:=CreateMetadataDataset(IO,AOwner)
else if (IO.Resource=FConnectionResource) then
Result:=CreateConnectionDataset(IO,AOwner)
else if (IO.Resource=FMetadataDetailResource) then
begin
if IO.GetVariable('ID',RN,[vsRoute,vsQuery])=vsNone then
@ -1220,12 +1647,25 @@ Var
H : TSQLDBRestDBHandler;
l,o : Int64;
begin
if MustLog(rloResource) then
DoLog(rloResource,IO,'Resource: %s; Operation: %s',[IO.ResourceName,RestMethods[IO.Operation]]);
H:=Nil;
Conn:=GetSQLConnection(aConnection,Tr);
try
IO.SetConn(Conn,TR);
Try
if MustLog(rloConnection) then
if Assigned(Conn) then
DoLog(rloConnection,IO,'Using connection to Host: %s; Database: %s',[Conn.HostName,Conn.DatabaseName])
else
DoLog(rloConnection,IO,'Resource %s does not require connection',[IO.ResourceName]);
if assigned(Conn) and MustLog(rloSQL) then
begin
Conn.LogEvents:=LogSQLOptions;
Conn.OnLog:=@IO.DoSQLLog;
end;
if (rdoHandleCORS in DispatchOptions) then
IO.Response.SetCustomHeader('Access-Control-Allow-Origin',ResolvedCORSAllowedOrigins);
if not AuthenticateRequest(IO,True) then
@ -1243,7 +1683,8 @@ begin
end;
H.ExecuteOperation;
DoHandleEvent(False,IO);
tr.Commit;
if Assigned(TR) then
TR.Commit;
SetDefaultResponseCode(IO);
except
TR.RollBack;
@ -1365,7 +1806,7 @@ begin
begin
IO.SetResource(Resource);
Connection:=FindConnection(IO);
if Connection=Nil then
if (Connection=Nil) and not IsSpecialResource(Resource) then
begin
if (rdoConnectionInURL in DispatchOptions) then
CreateErrorContent(IO,FStatus.GetStatusCode(rsNoConnectionSpecified),Format(SErrNoconnection,[GetConnectionName(IO)]))
@ -1396,8 +1837,13 @@ procedure TSQLDBRestDispatcher.UnRegisterRoutes;
begin
Un(FListRoute);
Un(FItemRoute);
Un(FConnectionItemRoute);
Un(FConnectionsRoute);
Un(FMetadataItemRoute);
Un(FMetadataRoute);
end;
procedure TSQLDBRestDispatcher.RegisterRoutes;
begin
if (FListRoute<>Nil) then
@ -1456,6 +1902,7 @@ Var
B : TRestBasicAuthenticator;
A : TRestAuthenticator;
begin
A:=Nil;
B:=Nil;
@ -1473,7 +1920,14 @@ begin
begin
Result:=(A.NeedConnection<>Delayed);
If Not Result then
Result:=A.AuthenticateRequest(IO)
begin
Result:=A.AuthenticateRequest(IO);
if MustLog(rloAuthentication) then
if Result then
DoLog(rloAuthentication,IO,'Authenticated user: %s',[IO.UserID])
else
DoLog(rloAuthentication,IO,'Authentication failed for user: %s',[TRestBasicAuthenticator.ExtractUserName(IO.Request)]);
end;
end;
finally
if Assigned(B) then
@ -1506,6 +1960,7 @@ begin
// First output, then input
IO.RestOutput.InitStreaming;
IO.RestInput.InitStreaming;
IO.OnSQLLog:=@Self.DoSQLLog;
if AuthenticateRequest(IO,False) then
DoHandleRequest(IO)
except
@ -1513,12 +1968,19 @@ begin
HandleException(E,IO);
end;
Finally
// Make sure there is a document in case of error
if (aResponse.ContentStream.Size=0) and Not ((aResponse.Code div 100)=2) then
IO.RESTOutput.CreateErrorContent(aResponse.Code,aResponse.CodeText);
if Not (IO.Operation in [roOptions,roHEAD]) then
IO.RestOutput.FinalizeOutput;
aResponse.ContentStream.Position:=0;
aResponse.ContentLength:=aResponse.ContentStream.Size;
if not aResponse.ContentSent then
aResponse.SendContent;
if MustLog(rloResultStatus) then
DoLog(rloResultStatus,IO,'Resource: %s; Operation: %s; Status: %d; Text: %s',[IO.ResourceName,RestMethods[IO.Operation],aResponse.Code,aResponse.CodeText]);
IO.Free;
end;
end;
@ -1651,7 +2113,7 @@ begin
Items[aIndex]:=aValue;
end;
function TSQLDBRestConnectionList.IndexOfConnection(const aName: string
function TSQLDBRestConnectionList.IndexOfConnection(const aName: UTF8string
): Integer;
begin
Result:=Count-1;
@ -1659,7 +2121,7 @@ begin
Dec(Result);
end;
function TSQLDBRestConnectionList.FindConnection(const aName: string): TSQLDBRestConnection;
function TSQLDBRestConnectionList.FindConnection(const aName: UTF8string): TSQLDBRestConnection;
Var
Idx : Integer;
@ -1849,6 +2311,8 @@ begin
Role:=C.Role;
DatabaseName:=C.DatabaseName;
ConnectionType:=C.ConnectionType;
Port:=C.Port;
SchemaName:=C.SchemaName;
Params.Assign(C.Params);
end
else

View File

@ -47,11 +47,14 @@ Type
FResource : TSQLDBRestResource;
FOwnsResource : Boolean;
procedure SetExternalDataset(AValue: TDataset);
function StreamRecord(O: TRestOutputStreamer; D: TDataset; FieldList: TRestFieldPairArray): Boolean;
Protected
function StreamRecord(O: TRestOutputStreamer; D: TDataset; FieldList: TRestFieldPairArray): Boolean; virtual;
function FindExistingRecord(D: TDataset): Boolean;
procedure CreateResourceFromDataset(D: TDataset); virtual;
procedure DoNotFound; virtual;
procedure SetPostParams(aParams: TParams; Old : TFields = Nil);virtual;
procedure SetPostFields(aFields: TFields);virtual;
procedure SetFieldFromData(DataField: TField; ResField: TSQLDBRestField; D: TJSONData); virtual;
procedure InsertNewRecord; virtual;
procedure UpdateExistingRecord(OldData: TDataset); virtual;
Procedure Notification(AComponent: TComponent; Operation: TOperation); override;
@ -81,7 +84,7 @@ Type
Function GetLimitOffset(out aLimit, aOffset: Int64) : Boolean; virtual;
Procedure Init(aIO: TRestIO; aStrings : TRestStringsConfig;AQueryClass : TSQLQueryClass); virtual;
Procedure ExecuteOperation;
Function StreamDataset(O: TRestOutputStreamer; D: TDataset; FieldList: TRestFieldPairArray) : Int64;
Function StreamDataset(O: TRestOutputStreamer; D: TDataset; FieldList: TRestFieldPairArray; CurrentOnly : Boolean = False) : Int64;
procedure SetParamFromData(P: TParam; F: TSQLDBRestField; D: TJSONData); virtual;
function GetDataForParam(P: TParam; F: TSQLDBRestField; Sources : TVariableSources = AllVariableSources): TJSONData; virtual;
Function GetString(aString : TRestStringProperty) : UTF8String;
@ -98,7 +101,7 @@ Type
implementation
uses strutils, dateutils, base64, sqldbrestconst;
uses strutils, variants, dateutils, base64, sqldbrestconst;
Const
@ -170,7 +173,8 @@ begin
end;
end;
function TSQLDBRestDBHandler.GetWhere(Out FilteredFields : TRestFilterPairArray): UTF8String;
function TSQLDBRestDBHandler.GetWhere(out FilteredFields: TRestFilterPairArray
): UTF8String;
Const
MaxFilterCount = 1+ Ord(High(TRestFieldFilter)) - Ord(Low(TRestFieldFilter));
@ -350,7 +354,8 @@ begin
end;
end;
Function TSQLDBRestDBHandler.GetDataForParam(P : TParam; F : TSQLDBRestField; Sources : TVariableSources = AllVariableSources) : TJSONData;
function TSQLDBRestDBHandler.GetDataForParam(P: TParam; F: TSQLDBRestField;
Sources: TVariableSources): TJSONData;
Var
vs : TVariableSource;
@ -380,7 +385,8 @@ begin
end;
end;
Procedure TSQLDBRestDBHandler.SetParamFromData(P : TParam; F : TSQLDBRestField; D : TJSONData);
procedure TSQLDBRestDBHandler.SetParamFromData(P: TParam; F: TSQLDBRestField;
D: TJSONData);
begin
if not Assigned(D) then
@ -408,7 +414,8 @@ begin
P.AsString:=D.AsString;
end;
Function TSQLDBRestDBHandler.FindFieldForParam(aOperation : TRestOperation; P : TParam) : TSQLDBRestField;
function TSQLDBRestDBHandler.FindFieldForParam(aOperation: TRestOperation;
P: TParam): TSQLDBRestField;
Var
N : UTF8String;
@ -490,13 +497,14 @@ begin
end;
end;
Function TSQLDBRestDBHandler.GetLimitOffset(Out aLimit,aOffset : Int64) : Boolean;
function TSQLDBRestDBHandler.GetLimitOffset(out aLimit, aOffset: Int64
): Boolean;
begin
Result:=IO.GetLimitOffset(EnforceLimit,aLimit,aoffset);
end;
Function TSQLDBRestDBHandler.GetLimit : UTF8String;
function TSQLDBRestDBHandler.GetLimit: UTF8String;
var
aOffset, aLimit : Int64;
@ -526,7 +534,8 @@ begin
end;
Function TSQLDBRestDBHandler.StreamRecord(O : TRestOutputStreamer; D : TDataset; FieldList : TRestFieldPairArray) : Boolean;
function TSQLDBRestDBHandler.StreamRecord(O: TRestOutputStreamer; D: TDataset;
FieldList: TRestFieldPairArray): Boolean;
Var
i : Integer;
@ -541,7 +550,8 @@ begin
O.EndRow;
end;
Function TSQLDBRestDBHandler.StreamDataset(O : TRestOutputStreamer; D : TDataset; FieldList : TRestFieldPairArray) : Int64;
function TSQLDBRestDBHandler.StreamDataset(O: TRestOutputStreamer; D: TDataset;
FieldList: TRestFieldPairArray; CurrentOnly : Boolean = False): Int64;
Var
aLimit,aOffset : Int64;
@ -569,25 +579,31 @@ begin
if O.HasOption(ooMetadata) then
O.WriteMetadata(FieldList);
O.StartData;
if EmulateOffsetLimit then
While (aOffset>0) and not D.EOF do
begin
D.Next;
Dec(aOffset);
end;
While not (D.EOF or LimitReached) do
if CurrentOnly then
StreamRecord(O,D,FieldList)
else
begin
If StreamRecord(O,D,FieldList) then
if EmulateOffsetLimit then
While (aOffset>0) and not D.EOF do
begin
D.Next;
Dec(aOffset);
end;
While not (D.EOF or LimitReached) do
begin
Dec(aLimit);
inc(Result);
If StreamRecord(O,D,FieldList) then
begin
Dec(aLimit);
inc(Result);
end;
D.Next;
end;
D.Next;
end;
O.EndData;
end;
Function TSQLDBRestDBHandler.GetSpecialDatasetForResource(aFieldList : TRestFieldPairArray) : TDataset;
function TSQLDBRestDBHandler.GetSpecialDatasetForResource(
aFieldList: TRestFieldPairArray): TDataset;
Var
@ -612,7 +628,7 @@ begin
FExternalDataset.FreeNotification(Self);
end;
Function TSQLDBRestDBHandler.SpecialResource : Boolean;
function TSQLDBRestDBHandler.SpecialResource: Boolean;
begin
Result:=(ExternalDataset<>Nil) or Assigned(FResource.OnGetDataset);
@ -637,6 +653,7 @@ begin
SQL:=FResource.GetResolvedSQl(skSelect,aWhere,aOrderBy,aLimit);
Q:=CreateQuery(SQL);
Try
Q.UsePrimaryKeyAsKey:=False;
FillParams(roGet,Q,WhereFilterList);
Result:=Q;
except
@ -689,12 +706,76 @@ begin
end;
end;
Function TSQLDBRestDBHandler.GetGeneratorValue(Const aGeneratorName : String) : Int64;
function TSQLDBRestDBHandler.GetGeneratorValue(const aGeneratorName: String
): Int64;
begin
Result:=IO.Connection.GetNextValue(aGeneratorName,1);
end;
procedure TSQLDBRestDBHandler.SetPostFields(aFields : TFields);
Var
I : Integer;
FData : TField;
D : TJSONData;
RF : TSQLDBRestField;
V : UTF8string;
begin
// Another approach would be to create params for all fields,
// call setPostParams, and copy field data from all set params
// That would allow the use of checkparams...
For I:=0 to aFields.Count-1 do
try
D:=Nil;
FData:=aFields[i];
RF:=FResource.Fields.FindByFieldName(FData.FieldName);
if (RF<>Nil) then
begin
if (RF.GeneratorName<>'') then // Only when doing POST
D:=TJSONInt64Number.Create(GetGeneratorValue(RF.GeneratorName))
else
D:=IO.RESTInput.GetContentField(RF.PublicName);
end
else if IO.GetVariable(FData.Name,V,[vsContent,vsQuery])<>vsNone then
D:=TJSONString.Create(V);
if (D<>Nil) then
SetFieldFromData(FData,RF,D); // Use new value, if any
finally
D.Free;
end;
end;
procedure TSQLDBRestDBHandler.SetFieldFromData(DataField: TField; ResField: TSQLDBRestField; D: TJSONData);
begin
if not Assigned(D) then
DataField.Clear
else if Assigned(ResField) then
Case ResField.FieldType of
rftInteger : DataField.AsInteger:=D.AsInteger;
rftLargeInt : DataField.AsLargeInt:=D.AsInt64;
rftFloat : DataField.AsFloat:=D.AsFloat;
rftDate : DataField.AsDateTime:=ScanDateTime(GetString(rpDateFormat),D.AsString);
rftTime : DataField.AsDateTime:=ScanDateTime(GetString(rpTimeFormat),D.AsString);
rftDateTime : DataField.AsDateTime:=ScanDateTime(GetString(rpDateTimeFormat),D.AsString);
rftString : DataField.AsString:=D.AsString;
rftBoolean : DataField.AsBoolean:=D.AsBoolean;
rftBlob :
{$IFNDEF VER3_0}
DataField.AsBytes:=BytesOf(DecodeStringBase64(D.AsString));
{$ELSE}
DataField.AsString:=DecodeStringBase64(D.AsString);
{$ENDIF}
else
DataField.AsString:=D.AsString;
end
else
DataField.AsString:=D.AsString;
end;
procedure TSQLDBRestDBHandler.SetPostParams(aParams : TParams; Old : TFields = Nil);
Var
@ -712,7 +793,7 @@ begin
FOld:=Nil;
P:=aParams[i];
F:=FResource.Fields.FindByFieldName(P.Name);
If Assigned(Fold) then
If Assigned(Old) then
Fold:=Old.FindField(P.Name);
if (F<>Nil) then
begin
@ -744,19 +825,33 @@ Var
SQL : UTF8String;
begin
SQL:=FResource.GetResolvedSQl(skInsert,'','','');
S:=TSQLStatement.Create(Self);
try
S.Database:=IO.Connection;
S.Transaction:=IO.Transaction;
S.SQL.Text:=SQL;
SetPostParams(S.Params);
S.Execute;
PostParams.Assign(S.Params);
S.Transaction.Commit;
Finally
S.Free;
end;
if Assigned(ExternalDataset) then
begin
ExternalDataset.Append;
SetPostFields(ExternalDataset.Fields);
try
ExternalDataset.Post;
except
ExternalDataset.Cancel;
Raise;
end
end
else
begin
SQL:=FResource.GetResolvedSQl(skInsert,'','','');
S:=TSQLStatement.Create(Self);
try
S.Database:=IO.Connection;
S.Transaction:=IO.Transaction;
S.SQL.Text:=SQL;
SetPostParams(S.Params);
S.Execute;
PostParams.Assign(S.Params);
S.Transaction.Commit;
Finally
S.Free;
end;
end;
end;
procedure TSQLDBRestDBHandler.DoHandlePost;
@ -789,20 +884,68 @@ Var
SQl : String;
begin
SQL:=FResource.GetResolvedSQl(skUpdate,'','','');
S:=TSQLStatement.Create(Self);
try
S.Database:=IO.Connection;
S.Transaction:=IO.Transaction;
S.SQL.Text:=SQL;
SetPostParams(S.Params,OldData.Fields);
// Give user a chance to look at it.
FResource.CheckParams(io.RestContext,roPut,S.Params);
S.Execute;
S.Transaction.Commit;
finally
S.Free;
end;
if (OldData=ExternalDataset) then
begin
ExternalDataset.Edit;
try
SetPostFields(ExternalDataset.Fields);
ExternalDataset.Post;
except
ExternalDataset.Cancel;
Raise;
end
end
else
begin
SQL:=FResource.GetResolvedSQl(skUpdate,'','','');
S:=TSQLStatement.Create(Self);
try
S.Database:=IO.Connection;
S.Transaction:=IO.Transaction;
S.SQL.Text:=SQL;
SetPostParams(S.Params,OldData.Fields);
// Give user a chance to look at it.
FResource.CheckParams(io.RestContext,roPut,S.Params);
S.Execute;
S.Transaction.Commit;
finally
S.Free;
end;
end;
end;
Function TSQLDBRestDBHandler.FindExistingRecord(D : TDataset) : Boolean;
Var
KeyFields : String;
FieldList : TRestFilterPairArray;
FP : TRestFilterPair;
V : Variant;
I : Integer;
begin
D.Open;
if D<>ExternalDataset then
Result:=Not (D.BOF and D.EOF)
else
begin
GetIDWhere(FieldList);
V:=VarArrayCreate([0,Length(FieldList)-1],varVariant);
KeyFields:='';
I:=0;
For FP in FieldList do
begin
if KeyFields<>'' then
KeyFields:=KeyFields+';';
KeyFields:=KeyFields+FP.Field.FieldName;
if Assigned(FP.ValueParam) then
V[i]:=FP.ValueParam.Value
else
V[i]:=FP.Value;
Inc(i);
end;
Result:=D.Locate(KeyFields,V,[loCaseInsensitive]);
end;
end;
procedure TSQLDBRestDBHandler.DoHandlePut;
@ -819,18 +962,20 @@ begin
FieldList:=BuildFieldList(True);
D:=GetDatasetForResource(FieldList,True);
try
D.Open;
if (D.BOF and D.EOF) then
if not FindExistingRecord(D) then
begin
DoNotFound;
exit;
end;
UpdateExistingRecord(D);
// Now build response
FreeAndNil(D);
FieldList:=BuildFieldList(False);
D:=GetDatasetForResource(FieldList,True);
D.Open;
if D<>ExternalDataset then
begin;
FreeAndNil(D);
D:=GetDatasetForResource(FieldList,True);
FieldList:=BuildFieldList(False);
D.Open;
end;
IO.RESTOutput.OutputOptions:=IO.RESTOutput.OutputOptions-[ooMetadata];
StreamDataset(IO.RESTOutput,D,FieldList);
finally
@ -863,17 +1008,27 @@ Var
FilteredFields : TRestFilterPairArray;
begin
aWhere:=GetIDWhere(FilteredFields);
SQL:=FResource.GetResolvedSQl(skDelete,aWhere,'');
Q:=CreateQuery(SQL);
try
FillParams(roDelete,Q,FilteredFields);
Q.ExecSQL;
if Q.RowsAffected<>1 then
if Assigned(ExternalDataset) then
begin
If FindExistingRecord(ExternalDataset) then
ExternalDataset.Delete
else
DoNotFound;
finally
Q.Free;
end;
end
else
begin
aWhere:=GetIDWhere(FilteredFields);
SQL:=FResource.GetResolvedSQl(skDelete,aWhere,'');
Q:=CreateQuery(SQL);
try
FillParams(roDelete,Q,FilteredFields);
Q.ExecSQL;
if Q.RowsAffected<>1 then
DoNotFound;
finally
Q.Free;
end;
end;
end;
end.

View File

@ -73,7 +73,8 @@ Type
rpOutputFormat,
rpCustomViewResourceName,
rpCustomViewSQLParam,
rpXMLDocumentRoot
rpXMLDocumentRoot,
rpConnectionResourceName
);
TRestStringProperties = Set of TRestStringProperty;
@ -131,6 +132,7 @@ Type
Property CustomViewResourceName : UTF8string Index ord(rpCustomViewResourceName) Read GetRestPropName Write SetRestPropName Stored IsRestStringStored;
Property CustomViewSQLParam : UTF8string Index ord(rpCustomViewSQLParam) Read GetRestPropName Write SetRestPropName Stored IsRestStringStored;
Property XMLDocumentRoot : UTF8string Index ord(rpXMLDocumentRoot) Read GetRestPropName Write SetRestPropName Stored IsRestStringStored;
Property ConnectionResourceName : UTF8string Index ord(rpConnectionResourceName) Read GetRestPropName Write SetRestPropName Stored IsRestStringStored;
end;
TRestStatus = (rsError, // Internal logic/unexpected error (500)
@ -275,17 +277,18 @@ Type
end;
{ TRestIO }
TSQLLogNotifyEvent = Procedure (Sender : TObject; EventType : TDBEventType; Const Msg : String) of object;
TRestIO = Class
private
FConn: TSQLConnection;
FCOnnection: UTF8String;
FInput: TRestInputStreamer;
FOnSQLLog: TSQLLogNotifyEvent;
FOperation: TRestOperation;
FOutput: TRestOutputStreamer;
FRequest: TRequest;
FResource: TSQLDBRestResource;
FResourceName: UTF8String;
FResponse: TResponse;
FRestContext: TRestContext;
FRestStatuses: TRestStatusConfig;
@ -293,12 +296,15 @@ Type
FSchema: UTF8String;
FTrans: TSQLTransaction;
FContentStream : TStream;
function GetResourceName: UTF8String;
function GetUserID: String;
procedure SetUserID(AValue: String);
Protected
Public
Constructor Create(aRequest : TRequest; aResponse : TResponse); virtual;
Destructor Destroy; override;
// Log callback for SQL. Rerouted here, because we need IO
procedure DoSQLLog(Sender: TSQLConnection; EventType: TDBEventType; const Msg: String);
// Set things.
Procedure SetIO(aInput : TRestInputStreamer;aOutput : TRestOutputStreamer);
Procedure SetConn(aConn : TSQLConnection; ATrans : TSQLTransaction);
@ -332,10 +338,12 @@ Type
Property RequestContentStream : TStream Read FContentStream;
Property RestContext : TRestContext Read FRestContext;
// For informative purposes
Property ResourceName : UTF8String Read FResourceName;
Property ResourceName : UTF8String Read GetResourceName;
Property Schema : UTF8String Read FSchema;
Property ConnectionName : UTF8String Read FCOnnection;
Property UserID : String Read GetUserID Write SetUserID;
// For logging
Property OnSQLLog :TSQLLogNotifyEvent Read FOnSQLLog Write FOnSQLLog;
end;
TRestIOClass = Class of TRestIO;
@ -430,7 +438,8 @@ Const
'fmt', { rpOutputFormat }
'customview', { rpCustomViewResourceName }
'sql', { rpCustomViewSQLParam }
'datapacket' { rpXMLDocumentRoot}
'datapacket', { rpXMLDocumentRoot}
'_connection' { rpConnectionResourceName }
);
DefaultStatuses : Array[TRestStatus] of Word = (
500, { rsError }
@ -895,6 +904,14 @@ begin
Result:=FRestContext.UserID;
end;
function TRestIO.GetResourceName: UTF8String;
begin
if Assigned(FResource) then
Result:=FResource.ResourceName
else
Result:='?';
end;
constructor TRestIO.Create(aRequest: TRequest; aResponse: TResponse);
begin
FRequest:=aRequest;
@ -917,6 +934,13 @@ begin
inherited Destroy;
end;
procedure TRestIO.DoSQLLog(Sender: TSQLConnection; EventType: TDBEventType; const Msg: String);
begin
If Assigned(OnSQLLog) then
FOnSQLLog(Self,EventType,Msg);
end;
function TRestIO.CreateRestContext : TRestContext;
begin

View File

@ -192,7 +192,7 @@ begin
if FRow=Nil then
Raise ESQLDBRest.CreateFmt(Statuses.GetStatusCode(rsError),SErrFieldWithoutRow,[N]);
D:=FieldToJSON(aPair);
if (D=Nil) and ((not HasOption(ooSparse)) or (FRow is TJSONArray)) then
if (D=Nil) and ((FRow is TJSONArray) or not HasOption(ooSparse)) then
D:=TJSONNull.Create;
if D<>Nil then
If FRow is TJSONArray then

View File

@ -197,7 +197,7 @@ Type
Function AllowResource(aContext : TBaseRestContext) : Boolean;
Function GetAllowedOperations(aContext : TBaseRestContext) : TRestOperations;
Function GetHTTPAllow : String; virtual;
function GetFieldList(aListKind: TFieldListKind): UTF8String;
function GetFieldList(aListKind: TFieldListKind; ASep : String = ''): UTF8String;
function GetFieldArray(aListKind: TFieldListKind): TSQLDBRestFieldArray;
Function GetResolvedSQl(aKind : TSQLKind; Const AWhere : UTF8String; Const aOrderBy : UTF8String = ''; aLimit : UTF8String = '') : UTF8String;
Procedure PopulateFieldsFromFieldDefs(Defs : TFieldDefs; aIndexFields : TStringArray; aProcessIdentifier : TProcessIdentifier; aMinFieldOpts : TRestFieldOptions);
@ -332,6 +332,7 @@ Type
Const
TypeNames : Array[TRestFieldType] of string = ('?','int','bigint','float','date','time','datetime','string','bool','blob');
RestMethods : Array[TRestOperation] of string = ('','GET','POST','PUT','DELETE','OPTIONS','HEAD');
implementation
@ -1051,8 +1052,6 @@ function TSQLDBRestResource.GetHTTPAllow: String;
Result:=Result+S;
end;
Const
Methods : Array[TRestOperation] of string = ('','GET','POST','PUT','DELETE','OPTIONS','HEAD');
Var
O : TRestOperation;
@ -1061,10 +1060,10 @@ begin
Result:='';
For O in TRestOperation do
if (O<>roUnknown) and (O in AllowedOperations) then
AddR(Methods[O]);
AddR(RestMethods[O]);
end;
function TSQLDBRestResource.GetFieldList(aListKind : TFieldListKind) : UTF8String;
function TSQLDBRestResource.GetFieldList(aListKind : TFieldListKind; ASep : String = '') : UTF8String;
Const
SepComma = ', ';
@ -1072,7 +1071,7 @@ Const
SepSpace = ' ';
Const
Seps : Array[TFieldListKind] of string = (sepComma,sepComma,sepComma,sepComma,sepAnd,sepSpace,sepComma);
DefaultSeps : Array[TFieldListKind] of string = (sepComma,sepComma,sepComma,sepComma,sepAnd,sepSpace,sepComma);
Const
Wheres = [flWhereKey];
@ -1080,15 +1079,20 @@ Const
UseEqual = Wheres+[flUpdate];
Var
Term,Res,Prefix : UTF8String;
Sep,Term,Res,Prefix : UTF8String;
I : Integer;
F : TSQLDBRestField;
begin
Prefix:='';
Sep:=aSep;
if Sep='' then
begin
Sep:=DefaultSeps[aListKind];
If aListKind in Colons then
Prefix:=':';
end;
Res:='';
If aListKind in Colons then
Prefix:=':';
For I:=0 to Fields.Count-1 do
begin
Term:='';
@ -1096,7 +1100,7 @@ begin
if F.UseInFieldList(aListKind) then
begin
Term:=Prefix+F.FieldName;
if aListKind in UseEqual then
if (aSep='') and (aListKind in UseEqual) then
begin
Term := F.FieldName+' = '+Term;
if (aListKind in Wheres) then
@ -1106,7 +1110,7 @@ begin
if (Term<>'') then
begin
If (Res<>'') then
Res:=Res+Seps[aListKind];
Res:=Res+Sep;
Res:=Res+Term;
end;
end;

View File

@ -1,6 +1,6 @@
{
This file is part of the Free Component Library (FCL)
Copyright (c) 2018 by Michael Van Canneyt
Copyright (c) 2019 by Michael Van Canneyt
Pascal to Javascript converter class.
@ -1455,6 +1455,8 @@ type
function IsForInExtArray(Loop: TPasImplForLoop; const VarResolved,
InResolved: TPasResolverResult; out ArgResolved, LengthResolved,
PropResultResolved: TPasResolverResult): boolean;
function IsHelperMethod(El: TPasElement): boolean; override;
function IsHelperForMember(El: TPasElement): boolean;
end;
//------------------------------------------------------------------------------
@ -2554,6 +2556,13 @@ var
Result:=false;
end;
procedure HandleEscape;
begin
inc(MyTokenPos);
if (MyTokenPos<=l) and (s[MyTokenPos]>#31) then
inc(MyTokenPos);
end;
begin
SetCurTokenString('');
s:=CurLine;
@ -2572,6 +2581,8 @@ begin
if MyTokenPos>l then
if DoEndOfLine then exit;
case s[MyTokenPos] of
'\':
HandleEscape;
'''':
begin
inc(MyTokenPos);
@ -2579,6 +2590,8 @@ begin
if MyTokenPos>l then
Error(nErrOpenString,SErrOpenString);
case s[MyTokenPos] of
'\':
HandleEscape;
'''':
begin
inc(MyTokenPos);
@ -2601,6 +2614,8 @@ begin
if MyTokenPos>l then
Error(nErrOpenString,SErrOpenString);
case s[MyTokenPos] of
'\':
HandleEscape;
'"':
begin
inc(MyTokenPos);
@ -2616,6 +2631,32 @@ begin
end;
until false;
end;
'`': // template literal
begin
inc(MyTokenPos);
repeat
while MyTokenPos>l do
if DoEndOfLine then
begin
writeln('AAA1 TPas2jsPasScanner.ReadNonPascalTillEndToken ',StopAtLineEnd);
if not StopAtLineEnd then
Error(nErrOpenString,SErrOpenString);
exit;
end;
case s[MyTokenPos] of
'\':
HandleEscape;
'`':
begin
inc(MyTokenPos);
break;
end;
// Note: template literals can span multiple lines
else
inc(MyTokenPos);
end;
until false;
end;
'/':
begin
inc(MyTokenPos);
@ -3189,6 +3230,13 @@ end;
procedure TPas2JSResolver.AddRecordType(El: TPasRecordType);
begin
inherited;
if (El.Name='') and (El.Parent.ClassType<>TPasVariant) then
begin
{$IFDEF VerbosePas2JS}
writeln('TPas2JSResolver.AddRecordType ',GetObjName(El.Parent));
{$ENDIF}
RaiseNotYetImplemented(20190408224556,El,'anonymous record type');
end;
if El.Parent is TProcedureBody then
// local record
AddElevatedLocal(El);
@ -3987,19 +4035,25 @@ begin
RaiseMsg(20180329141108,nInvalidXModifierY,
sInvalidXModifierY,[Proc.ElementTypeName,ModifierNames[pm]],Proc);
end;
okClassHelper:
okClassHelper,okRecordHelper,okTypeHelper:
begin
HelperForType:=ResolveAliasType(AClass.HelperForType);
if HelperForType.ClassType<>TPasClassType then
RaiseNotYetImplemented(20190201165157,El);
if TPasClassType(HelperForType).IsExternal then
if HelperForType.ClassType=TPasClassType then
begin
// method of a class helper for external class
if IsClassMethod(El) and not (ptmStatic in El.Modifiers) then
RaiseMsg(20190201165259,nHelperClassMethodForExtClassMustBeStatic,
sHelperClassMethodForExtClassMustBeStatic,[],El);
if El.ClassType=TPasConstructor then
RaiseNotYetImplemented(20190206153655,El);
if TPasClassType(HelperForType).IsExternal then
begin
// method of a class helper for external class
if IsClassMethod(El) and not (ptmStatic in El.Modifiers) then
RaiseMsg(20190201165259,nHelperClassMethodForExtClassMustBeStatic,
sHelperClassMethodForExtClassMustBeStatic,[],El);
if El.ClassType=TPasConstructor then
RaiseNotYetImplemented(20190206153655,El);
end;
end;
if Proc.IsExternal then
begin
if not (HelperForType is TPasMembersType) then
RaiseMsg(20190314225457,nNotSupportedX,sNotSupportedX,['external method in type helper'],El);
end;
end;
end;
@ -5886,6 +5940,26 @@ begin
CheckAssignResCompatibility(VarResolved,PropResultResolved,Loop.VariableName,true);
end;
function TPas2JSResolver.IsHelperMethod(El: TPasElement): boolean;
begin
Result:=inherited IsHelperMethod(El);
if not Result then exit;
Result:=not TPasProcedure(El).IsExternal;
end;
function TPas2JSResolver.IsHelperForMember(El: TPasElement): boolean;
begin
if (El=nil) or (El.Parent=nil) or (El.Parent.ClassType<>TPasClassType)
or (TPasClassType(El.Parent).HelperForType=nil) then
exit(false);
if El is TPasProcedure then
Result:=TPasProcedure(El).IsExternal
else if El is TPasVariable then
Result:=vmExternal in TPasVariable(El).VarModifiers
else
Result:=true;
end;
{ TParamContext }
constructor TParamContext.Create(PasEl: TPasElement; JSEl: TJSElement;
@ -6555,15 +6629,17 @@ end;
function TPasToJSConverter.CreateFreeOrNewInstanceExpr(Ref: TResolvedReference;
AContext: TConvertContext): TJSCallExpression;
// create "$create("funcname");"
// class: create "$create("ProcName")"
// record: create "$new().ProcName()"
var
C: TJSCallExpression;
C, SubCall: TJSCallExpression;
Proc: TPasProcedure;
ProcScope: TPasProcedureScope;
ClassRecScope: TPasClassOrRecordScope;
ClassOrRec: TPasElement;
ArgEx: TJSLiteral;
FunName: String;
FunName, ProcName: String;
DotExpr: TJSDotMemberExpression;
begin
Result:=nil;
//writeln('TPasToJSConverter.CreateFreeOrNewInstanceExpr Ref.Declaration=',GetObjName(Ref.Declaration));
@ -6579,16 +6655,33 @@ begin
RaiseInconsistency(20170125191923,ClassOrRec);
C:=CreateCallExpression(Ref.Element);
try
// add "$create()"
if rrfNewInstance in Ref.Flags then
FunName:=GetBIName(pbifnClassInstanceNew)
ProcName:=TransformVariableName(Proc,AContext);
if ClassOrRec.ClassType=TPasRecordType then
begin
// create "path.$new()"
FunName:=CreateReferencePath(Proc,AContext,rpkPathWithDot,false,Ref)+GetBIName(pbifnRecordNew);
SubCall:=CreateCallExpression(Ref.Element);
SubCall.Expr:=CreatePrimitiveDotExpr(FunName,Ref.Element);
// append ".ProcName"
DotExpr:=TJSDotMemberExpression(CreateElement(TJSDotMemberExpression,Ref.Element));
DotExpr.MExpr:=SubCall;
DotExpr.Name:=TJSString(ProcName);
// as call: "path.$new().ProcName()"
C.Expr:=DotExpr;
end
else
FunName:=GetBIName(pbifnClassInstanceFree);
FunName:=CreateReferencePath(Proc,AContext,rpkPathWithDot,false,Ref)+FunName;
C.Expr:=CreatePrimitiveDotExpr(FunName,Ref.Element);
// parameter: "funcname"
ArgEx := CreateLiteralString(Ref.Element,TransformVariableName(Proc,AContext));
C.AddArg(ArgEx);
begin
// add "$create()"
if rrfNewInstance in Ref.Flags then
FunName:=GetBIName(pbifnClassInstanceNew)
else
FunName:=GetBIName(pbifnClassInstanceFree);
FunName:=CreateReferencePath(Proc,AContext,rpkPathWithDot,false,Ref)+FunName;
C.Expr:=CreatePrimitiveDotExpr(FunName,Ref.Element);
// parameter: "ProcName"
ArgEx := CreateLiteralString(Ref.Element,ProcName);
C.AddArg(ArgEx);
end;
Result:=C;
finally
if Result=nil then
@ -7821,7 +7914,7 @@ begin
else if aResolver.IsExternalClassConstructor(RightRefDecl) then
begin
// e.g. mod.ExtClass.new;
if El.Parent is TParamsExpr then
if (El.Parent is TParamsExpr) and (TParamsExpr(El.Parent).Value=El) then
// Note: ExtClass.new() is handled in ConvertFuncParams
RaiseNotSupported(El,AContext,20190116135818);
Result:=ConvertExternalConstructor(El.left,RightRef,nil,AContext);
@ -7896,7 +7989,8 @@ begin
if aResolver.IsHelper(RightRefDecl.Parent) then
begin
// LeftJS.HelperMember
if RightRefDecl is TPasVariable then
if (RightRefDecl is TPasVariable)
and not (vmExternal in TPasVariable(RightRefDecl).VarModifiers) then
begin
// LeftJS.HelperField -> HelperType.HelperField
if Assigned(OnConvertRight) then
@ -7907,7 +8001,10 @@ begin
end
else if RightRefDecl is TPasProcedure then
begin
if rrfNoImplicitCallWithoutParams in RightRef.Flags then
Proc:=TPasProcedure(RightRefDecl);
if Proc.IsExternal then
// normal call
else if rrfNoImplicitCallWithoutParams in RightRef.Flags then
begin
Result:=CreateReferencePathExpr(RightRefDecl,AContext);
exit;
@ -7915,7 +8012,6 @@ begin
else
begin
// call helper method
Proc:=TPasProcedure(RightRefDecl);
Result:=CreateCallHelperMethod(Proc,El,AContext);
exit;
end;
@ -8255,10 +8351,16 @@ begin
if TargetProcType.Args.Count>0 then
begin
// add default parameters:
// insert array parameter [], e.g. this.TObject.$create("create",[])
ArrLit:=TJSArrayLiteral(CreateElement(TJSArrayLiteral,El));
CreateProcedureCallArgs(ArrLit.Elements,nil,TargetProcType,AContext);
Call.AddArg(ArrLit);
if Decl.Parent.ClassType=TPasRecordType then
// insert default parameters, e.g. TRecord.$new().create(1,2,3)
CreateProcedureCallArgs(Call.Args.Elements,nil,TargetProcType,AContext)
else
begin
// insert array parameter [], e.g. TObject.$create("create",[])
ArrLit:=TJSArrayLiteral(CreateElement(TJSArrayLiteral,El));
CreateProcedureCallArgs(ArrLit.Elements,nil,TargetProcType,AContext);
Call.AddArg(ArrLit);
end;
end;
exit;
end;
@ -8295,7 +8397,7 @@ begin
Decl:=aResolver.GetPasPropertySetter(Prop);
if Decl is TPasProcedure then
begin
if aResolver.IsHelper(Decl.Parent) then
if aResolver.IsHelperMethod(Decl) then
begin
Result:=CreateCallHelperMethod(TPasProcedure(Decl),El,AContext);
exit;
@ -9620,7 +9722,8 @@ var
end;
if Call=nil then
Call:=CreateFreeOrNewInstanceExpr(Ref,AContext);
if rrfNewInstance in Ref.Flags then
if (rrfNewInstance in Ref.Flags)
and (Ref.Declaration.Parent.ClassType=TPasClassType) then
begin
// insert array parameter [], e.g. this.TObject.$create("create",[])
JsArrLit:=TJSArrayLiteral(CreateElement(TJSArrayLiteral,El));
@ -9768,7 +9871,7 @@ begin
end
else if C.InheritsFrom(TPasProcedure) then
begin
if aResolver.IsHelper(Decl.Parent) then
if aResolver.IsHelperMethod(Decl) then
begin
// calling a helper method
Result:=CreateCallHelperMethod(TPasProcedure(Decl),El.Value,AContext);
@ -16187,7 +16290,7 @@ begin
Result:=CreateReferencePathExpr(Proc,AContext);
exit;
end;
IsHelper:=aResolver.IsHelper(Proc.Parent);
IsHelper:=aResolver.IsHelperMethod(Proc);
NeedClass:=aResolver.IsClassMethod(Proc) and not aResolver.MethodIsStatic(Proc);
// an of-object method -> create "rtl.createCallback(Target,func)"
@ -16599,7 +16702,7 @@ begin
if Decl is TPasFunction then
begin
// call function
if aResolver.IsHelper(Decl.Parent) then
if aResolver.IsHelperMethod(Decl) then
begin
if (Expr=nil) then
// implicit property read, e.g. enumerator property Current
@ -21304,9 +21407,16 @@ var
begin
if (Ref=nil) or (Ref.WithExprScope=nil) then exit(false);
Parent:=El.Parent;
if (Parent<>nil) and (Parent.ClassType=TPasClassType)
if (Parent.ClassType=TPasClassType)
and (TPasClassType(Parent).HelperForType<>nil) then
exit(false);
begin
// e.g. with Obj do HelperMethod
if aResolver.IsHelperForMember(El) then
// e.g. with Obj do HelperExternalMethod -> Obj.HelperCall
else
// e.g. with Obj do HelperMethod -> THelper.HelperCall
exit(false);
end;
Result:=true;
end;
@ -21493,38 +21603,25 @@ begin
begin
ParentEl:=ImplToDecl(ParentEl);
IsClassRec:=(ParentEl.ClassType=TPasClassType)
or (ParentEl.ClassType=TPasRecordType);
// check if ParentEl has a JS var
ShortName:=AContext.GetLocalName(ParentEl);
//writeln('TPasToJSConverter.CreateReferencePath El=',GetObjName(El),' ParentEl=',GetObjName(ParentEl),' ShortName=',ShortName);
IsClassRec:=(ParentEl.ClassType=TPasClassType)
or (ParentEl.ClassType=TPasRecordType);
if (ShortName<>'') and not IsClassRec then
begin
Prepend(Result,ShortName);
break;
end
else if ParentEl.ClassType=TImplementationSection then
begin
// element is in an implementation section (not program/library section)
// in other unit -> use pas.unitname.$impl
FoundModule:=El.GetModule;
if FoundModule=nil then
RaiseInconsistency(20161024192755,El);
Prepend(Result,TransformModuleName(FoundModule,true,AContext)
+'.'+GetBIName(pbivnImplementation));
break;
end
else if ParentEl is TPasModule then
begin
// element is in an unit interface or program/library section
Prepend(Result,TransformModuleName(TPasModule(ParentEl),true,AContext));
break;
end
else if IsClassRec then
if IsClassRec then
begin
// parent is a class or record declaration
if (ParentEl.ClassType=TPasClassType)
and (TPasClassType(ParentEl).HelperForType<>nil)
and aResolver.IsHelperForMember(El) then
begin
// redirect to helper-for-type
ParentEl:=aResolver.ResolveAliasType(TPasClassType(ParentEl).HelperForType);
ShortName:=AContext.GetLocalName(ParentEl);
end;
if Full then
Prepend(Result,ParentEl.Name)
else
@ -21541,8 +21638,10 @@ begin
Prepend(Result,ParentEl.Name)
else if (ParentEl.ClassType=TPasClassType)
and (TPasClassType(ParentEl).HelperForType<>nil) then
begin
// helpers have no self
Prepend(Result,ParentEl.Name)
Prepend(Result,ParentEl.Name);
end
else if (SelfContext<>nil)
and IsA(TPasType(SelfContext.ThisPas),TPasMembersType(ParentEl)) then
begin
@ -21575,6 +21674,28 @@ begin
break;
end;
end
else if (ShortName<>'') then
begin
Prepend(Result,ShortName);
break;
end
else if ParentEl.ClassType=TImplementationSection then
begin
// element is in an implementation section (not program/library section)
// in other unit -> use pas.unitname.$impl
FoundModule:=El.GetModule;
if FoundModule=nil then
RaiseInconsistency(20161024192755,El);
Prepend(Result,TransformModuleName(FoundModule,true,AContext)
+'.'+GetBIName(pbivnImplementation));
break;
end
else if ParentEl is TPasModule then
begin
// element is in an unit interface or program/library section
Prepend(Result,TransformModuleName(TPasModule(ParentEl),true,AContext));
break;
end
else if ParentEl.ClassType=TPasEnumType then
begin
if (ShortName<>'') and not Full then

View File

@ -1,4 +1,4 @@
{ Author: Mattias Gaertner 2018 mattias@freepascal.org
{ Author: Mattias Gaertner 2019 mattias@freepascal.org
Abstract:
TPas2jsCompiler is the wheel boss of the pas2js compiler.
@ -88,7 +88,7 @@ const
nSrcMapBaseDirIs = 135; sSrcMapBaseDirIs = 'source map "local base directory" is %s';
nUnitFileNotFound = 136; sUnitFileNotFound = 'unit file not found %s';
nClassInterfaceStyleIs = 137; sClassInterfaceStyleIs = 'Class interface style is %s';
// was nMacroXSetToY = 138
nHandlingEnvOpts = 138; sHandlingEnvOpts = 'handling environment options %s';
nPostProcessorInfoX = 139; sPostProcessorInfoX = 'Post processor: %s';
nPostProcessorRunX = 140; sPostProcessorRunX = 'Run post processor: %s';
nPostProcessorFailX = 141; sPostProcessorFailX = 'Post processor failed: %s';
@ -549,6 +549,7 @@ type
// params, cfg files
FCurParam: string;
procedure LoadConfig(CfgFilename: string);
procedure ReadEnvironment;
procedure ReadParam(Param: string; Quick, FromCmdLine: boolean);
procedure ReadSingleLetterOptions(const Param: string; p: integer;
const Allowed: string; out Enabled, Disabled: string);
@ -1673,30 +1674,211 @@ begin
// if Result=nil resolver will give a nice error position, so don't do it here
end;
{ TPas2jsCompiler }
{ TPas2JSConfigSupport }
procedure TPas2jsCompiler.SetFS(AValue: TPas2jsFS);
procedure TPas2JSConfigSupport.CfgSyntaxError(const Msg: string);
begin
if FFS=AValue then Exit;
FOwnsFS:=false;
FFS:=AValue;
Compiler.Log.Log(mtError,Msg,0,CurrentCfgFilename,CurrentCfgLineNumber,0);
Compiler.Terminate(ExitCodeErrorInConfig);
end;
function TPas2jsCompiler.GetFileCount: integer;
begin
Result:=FFiles.Count;
end;
function TPas2jsCompiler.GetDefaultNamespace: String;
procedure TPas2JSConfigSupport.LoadConfig(Const aFileName: String);
type
TSkip = (
skipNone,
skipIf,
skipElse
);
const
IdentChars = ['a'..'z','A'..'Z','_','0'..'9'];
var
C: TClass;
Line: String;
l, p, StartP: integer;
function GetWord: String;
begin
StartP:=p;
while (p<=l) and ((Line[p] in IdentChars) or (Line[p]>#127)) do inc(p);
Result:=copy(Line,StartP,p-StartP);
while (p<=l) and (Line[p] in [' ',#9]) do inc(p);
end;
procedure DebugCfgDirective(const s: string);
begin
Compiler.Log.LogMsg(nCfgDirective,[QuoteStr(Line),s],CurrentCfgFilename,CurrentCfgLineNumber,1,false);
end;
var
OldCfgFilename, Directive, aName, Expr: String;
aFile: TSourceLineReader;
IfLvl, SkipLvl, OldCfgLineNumber: Integer;
Skip: TSkip;
begin
Result:='';
if FMainFile=nil then exit;
if FMainFile.PasModule=nil then exit;
C:=FMainFile.PasModule.ClassType;
if (C=TPasProgram) or (C=TPasLibrary) or (C=TPasPackage) then
Result:=FMainFile.PascalResolver.DefaultNameSpace;
if Compiler.ShowDebug or Compiler.ShowTriedUsedFiles then
Compiler.Log.LogMsgIgnoreFilter(nReadingOptionsFromFile,[QuoteStr(aFilename)]);
IfLvl:=0;
SkipLvl:=0;
Skip:=skipNone;
aFile:=nil;
try
OldCfgFilename:=FCurrentCfgFilename;
FCurrentCfgFilename:=aFilename;
OldCfgLineNumber:=FCurrentCfgLineNumber;
aFile:=GetReader(aFileName);
while not aFile.IsEOF do begin
Line:=aFile.ReadLine;
FCurrentCfgLineNumber:=aFile.LineNumber;
if Compiler.ShowDebug then
Compiler.Log.LogMsgIgnoreFilter(nInterpretingFileOption,[QuoteStr(Line)]);
if Line='' then continue;
l:=length(Line);
p:=1;
while (p<=l) and (Line[p] in [' ',#9]) do inc(p);
if p>l then continue; // empty line
if (p<=l) and (Line[p]='#') then
begin
// cfg directive
inc(p);
if (p>l) or (Line[p] in [#0,#9,' ','-']) then continue; // comment
Directive:=lowercase(GetWord);
case Directive of
'ifdef','ifndef':
begin
inc(IfLvl);
if Skip=skipNone then
begin
aName:=GetWord;
if Compiler.IsDefined(aName)=(Directive='ifdef') then
begin
// execute block
if Compiler.ShowDebug then
DebugCfgDirective('true -> execute');
end else begin
// skip block
if Compiler.ShowDebug then
DebugCfgDirective('false -> skip');
SkipLvl:=IfLvl;
Skip:=skipIf;
end;
end;
end;
'if':
begin
inc(IfLvl);
if Skip=skipNone then
begin
Expr:=copy(Line,p,length(Line));
if ConditionEvaluator.Eval(Expr) then
begin
// execute block
if Compiler.ShowDebug then
DebugCfgDirective('true -> execute');
end else begin
// skip block
if Compiler.ShowDebug then
DebugCfgDirective('false -> skip');
SkipLvl:=IfLvl;
Skip:=skipIf;
end;
end;
end;
'else':
begin
if IfLvl=0 then
CfgSyntaxError('"'+Directive+'" without #ifdef');
if (Skip=skipElse) and (IfLvl=SkipLvl) then
CfgSyntaxError('"there was already an #else');
if (Skip=skipIf) and (IfLvl=SkipLvl) then
begin
// if-block was skipped -> execute else block
if Compiler.ShowDebug then
DebugCfgDirective('execute');
SkipLvl:=0;
Skip:=skipNone;
end else if Skip=skipNone then
begin
// if-block was executed -> skip else block
if Compiler.ShowDebug then
DebugCfgDirective('skip');
Skip:=skipElse;
SkipLvl:=IfLvl;
end;
end;
'elseif':
begin
if IfLvl=0 then
CfgSyntaxError('"'+Directive+'" without #ifdef');
if (Skip=skipIf) and (IfLvl=SkipLvl) then
begin
// if-block was skipped -> try this elseif
Expr:=copy(Line,p,length(Line));
if ConditionEvaluator.Eval(Expr) then
begin
// execute elseif block
if Compiler.ShowDebug then
DebugCfgDirective('true -> execute');
SkipLvl:=0;
Skip:=skipNone;
end else begin
// skip elseif block
if Compiler.ShowDebug then
DebugCfgDirective('false -> skip');
end;
end else if Skip=skipNone then
begin
// if-block was executed -> skip without test
if Compiler.ShowDebug then
DebugCfgDirective('no test -> skip');
Skip:=skipIf;
end;
end;
'endif':
begin
if IfLvl=0 then
CfgSyntaxError('"'+Directive+'" without #ifdef');
dec(IfLvl);
if IfLvl<SkipLvl then
begin
// end block
if Compiler.ShowDebug then
DebugCfgDirective('end block');
SkipLvl:=0;
Skip:=skipNone;
end;
end;
'error':
Compiler.ParamFatal('user defined: '+copy(Line,p,length(Line)))
else
if Skip=skipNone then
CfgSyntaxError('unknown directive "#'+Directive+'"')
else
DebugCfgDirective('skipping unknown directive');
end;
end else if Skip=skipNone then
begin
// option line
Line:=copy(Line,p,length(Line));
Compiler.ReadParam(Line,false,false);
end;
end;
finally
FCurrentCfgFilename:=OldCfgFilename;
FCurrentCfgLineNumber:=OldCfgLineNumber;
aFile.Free;
end;
if Compiler.ShowDebug or Compiler.ShowTriedUsedFiles then
Compiler.Log.LogMsgIgnoreFilter(nEndOfReadingConfigFile,[QuoteStr(aFilename)]);
end;
procedure TPas2JSConfigSupport.LoadDefaultConfig;
var
aFileName: string;
begin
aFileName:=FindDefaultConfig;
if aFileName<>'' then
LoadConfig(aFilename);
end;
procedure TPas2JSConfigSupport.ConditionEvalLog(Sender: TCondDirectiveEvaluator;
@ -1736,6 +1918,32 @@ begin
Result:=false;
end;
{ TPas2jsCompiler }
procedure TPas2jsCompiler.SetFS(AValue: TPas2jsFS);
begin
if FFS=AValue then Exit;
FOwnsFS:=false;
FFS:=AValue;
end;
function TPas2jsCompiler.GetFileCount: integer;
begin
Result:=FFiles.Count;
end;
function TPas2jsCompiler.GetDefaultNamespace: String;
var
C: TClass;
begin
Result:='';
if FMainFile=nil then exit;
if FMainFile.PasModule=nil then exit;
C:=FMainFile.PasModule.ClassType;
if (C=TPasProgram) or (C=TPasLibrary) or (C=TPasPackage) then
Result:=FMainFile.PascalResolver.DefaultNameSpace;
end;
procedure TPas2jsCompiler.Compile(StartTime: TDateTime);
var
Checked: TPasAnalyzerKeySet;
@ -2752,7 +2960,7 @@ begin
r(mtInfo,nSrcMapBaseDirIs,sSrcMapBaseDirIs);
r(mtFatal,nUnitFileNotFound,sUnitFileNotFound);
r(mtInfo,nClassInterfaceStyleIs,sClassInterfaceStyleIs);
LastMsgNumber:=-1; ;// was nMacroXSetToY 138
r(mtInfo,nHandlingEnvOpts,sHandlingEnvOpts);
r(mtInfo,nPostProcessorInfoX,sPostProcessorInfoX);
r(mtInfo,nPostProcessorRunX,sPostProcessorRunX);
r(mtError,nPostProcessorFailX,sPostProcessorFailX);
@ -2762,215 +2970,29 @@ begin
Pas2jsPParser.RegisterMessages(Log);
end;
procedure TPas2JSConfigSupport.CfgSyntaxError(const Msg: string);
begin
Compiler.Log.Log(mtError,Msg,0,CurrentCfgFilename,CurrentCfgLineNumber,0);
Compiler.Terminate(ExitCodeErrorInConfig);
end;
procedure TPas2jsCompiler.LoadConfig(CfgFilename: string);
begin
ConfigSupport.LoadConfig(CfgFileName);
end;
procedure TPas2JSConfigSupport.LoadConfig(Const aFileName: String);
type
TSkip = (
skipNone,
skipIf,
skipElse
);
const
IdentChars = ['a'..'z','A'..'Z','_','0'..'9'];
procedure TPas2jsCompiler.ReadEnvironment;
var
Line: String;
l, p, StartP: integer;
function GetWord: String;
begin
StartP:=p;
while (p<=l) and ((Line[p] in IdentChars) or (Line[p]>#127)) do inc(p);
Result:=copy(Line,StartP,p-StartP);
while (p<=l) and (Line[p] in [' ',#9]) do inc(p);
end;
procedure DebugCfgDirective(const s: string);
begin
Compiler.Log.LogMsg(nCfgDirective,[QuoteStr(Line),s],CurrentCfgFilename,CurrentCfgLineNumber,1,false);
end;
var
OldCfgFilename, Directive, aName, Expr: String;
aFile: TSourceLineReader;
IfLvl, SkipLvl, OldCfgLineNumber: Integer;
Skip: TSkip;
s: String;
List: TStrings;
begin
if Compiler.ShowDebug or Compiler.ShowTriedUsedFiles then
Compiler.Log.LogMsgIgnoreFilter(nReadingOptionsFromFile,[QuoteStr(aFilename)]);
IfLvl:=0;
SkipLvl:=0;
Skip:=skipNone;
aFile:=nil;
s:=GetEnvironmentVariable('PAS2JS_OPTS');
if s='' then exit;
if ShowDebug then
Log.LogMsgIgnoreFilter(nHandlingEnvOpts,['PAS2JS_OPTS=['+s+']']);
List:=TStringList.Create;
try
OldCfgFilename:=FCurrentCfgFilename;
FCurrentCfgFilename:=aFilename;
OldCfgLineNumber:=FCurrentCfgLineNumber;
aFile:=GetReader(aFileName);
while not aFile.IsEOF do begin
Line:=aFile.ReadLine;
FCurrentCfgLineNumber:=aFile.LineNumber;
if Compiler.ShowDebug then
Compiler.Log.LogMsgIgnoreFilter(nInterpretingFileOption,[QuoteStr(Line)]);
if Line='' then continue;
l:=length(Line);
p:=1;
while (p<=l) and (Line[p] in [' ',#9]) do inc(p);
if p>l then continue; // empty line
if (p<=l) and (Line[p]='#') then
begin
// cfg directive
inc(p);
if (p>l) or (Line[p] in [#0,#9,' ','-']) then continue; // comment
Directive:=lowercase(GetWord);
case Directive of
'ifdef','ifndef':
begin
inc(IfLvl);
if Skip=skipNone then
begin
aName:=GetWord;
if Compiler.IsDefined(aName)=(Directive='ifdef') then
begin
// execute block
if Compiler.ShowDebug then
DebugCfgDirective('true -> execute');
end else begin
// skip block
if Compiler.ShowDebug then
DebugCfgDirective('false -> skip');
SkipLvl:=IfLvl;
Skip:=skipIf;
end;
end;
end;
'if':
begin
inc(IfLvl);
if Skip=skipNone then
begin
Expr:=copy(Line,p,length(Line));
if ConditionEvaluator.Eval(Expr) then
begin
// execute block
if Compiler.ShowDebug then
DebugCfgDirective('true -> execute');
end else begin
// skip block
if Compiler.ShowDebug then
DebugCfgDirective('false -> skip');
SkipLvl:=IfLvl;
Skip:=skipIf;
end;
end;
end;
'else':
begin
if IfLvl=0 then
CfgSyntaxError('"'+Directive+'" without #ifdef');
if (Skip=skipElse) and (IfLvl=SkipLvl) then
CfgSyntaxError('"there was already an #else');
if (Skip=skipIf) and (IfLvl=SkipLvl) then
begin
// if-block was skipped -> execute else block
if Compiler.ShowDebug then
DebugCfgDirective('execute');
SkipLvl:=0;
Skip:=skipNone;
end else if Skip=skipNone then
begin
// if-block was executed -> skip else block
if Compiler.ShowDebug then
DebugCfgDirective('skip');
Skip:=skipElse;
SkipLvl:=IfLvl;
end;
end;
'elseif':
begin
if IfLvl=0 then
CfgSyntaxError('"'+Directive+'" without #ifdef');
if (Skip=skipIf) and (IfLvl=SkipLvl) then
begin
// if-block was skipped -> try this elseif
Expr:=copy(Line,p,length(Line));
if ConditionEvaluator.Eval(Expr) then
begin
// execute elseif block
if Compiler.ShowDebug then
DebugCfgDirective('true -> execute');
SkipLvl:=0;
Skip:=skipNone;
end else begin
// skip elseif block
if Compiler.ShowDebug then
DebugCfgDirective('false -> skip');
end;
end else if Skip=skipNone then
begin
// if-block was executed -> skip without test
if Compiler.ShowDebug then
DebugCfgDirective('no test -> skip');
Skip:=skipIf;
end;
end;
'endif':
begin
if IfLvl=0 then
CfgSyntaxError('"'+Directive+'" without #ifdef');
dec(IfLvl);
if IfLvl<SkipLvl then
begin
// end block
if Compiler.ShowDebug then
DebugCfgDirective('end block');
SkipLvl:=0;
Skip:=skipNone;
end;
end;
'error':
Compiler.ParamFatal('user defined: '+copy(Line,p,length(Line)))
else
if Skip=skipNone then
CfgSyntaxError('unknown directive "#'+Directive+'"')
else
DebugCfgDirective('skipping unknown directive');
end;
end else if Skip=skipNone then
begin
// option line
Line:=copy(Line,p,length(Line));
Compiler.ReadParam(Line,false,false);
end;
end;
SplitCmdLineParams(s,List);
for s in List do
if s<>'' then
ReadParam(s,false,false);
finally
FCurrentCfgFilename:=OldCfgFilename;
FCurrentCfgLineNumber:=OldCfgLineNumber;
aFile.Free;
List.Free;
end;
if Compiler.ShowDebug or Compiler.ShowTriedUsedFiles then
Compiler.Log.LogMsgIgnoreFilter(nEndOfReadingConfigFile,[QuoteStr(aFilename)]);
end;
procedure TPas2JSConfigSupport.LoadDefaultConfig;
var
aFileName: string;
begin
aFileName:=FindDefaultConfig;
if aFileName<>'' then
LoadConfig(aFilename);
end;
procedure TPas2jsCompiler.ParamFatal(Msg: string);
@ -4068,6 +4090,9 @@ begin
if Assigned(ConfigSupport) and not SkipDefaultConfig then
ConfigSupport.LoadDefaultConfig;
// read env PAS2JS_OPTS
ReadEnvironment;
// read command line parameters
for i:=0 to ParamList.Count-1 do
ReadParam(ParamList[i],false,true);
@ -4313,6 +4338,8 @@ begin
w(' -? : Show this help');
w(' -h : Show this help');
Log.LogLn;
w('Environment variable PAS2JS_OPTS is parsed after default config and before command line parameters.');
Log.LogLn;
w('Macros: Format is $Name, $Name$ or $Name()');
for i:=0 to ParamMacros.Count-1 do begin
ParamMacro:=ParamMacros[i];

View File

@ -1494,7 +1494,7 @@ procedure TPas2jsFilesCache.WriteFoldersAndSearchPaths;
var
i: Integer;
begin
WriteFolder('working directory',GetCurrentDirPJ);
WriteFolder('working directory',BaseDirectory);
for i:=0 to ForeignUnitPaths.Count-1 do
WriteFolder('foreign unit path',ForeignUnitPaths[i]);
for i:=0 to UnitPaths.Count-1 do
@ -1915,6 +1915,7 @@ var
i: Integer;
aFilename: String;
begin
//writeln('TPas2jsFilesCache.FindUnitFileName "',aUnitname,'" ModuleDir="',ModuleDir,'"');
Result:='';
IsForeign:=false;
SearchedDirs:=TStringList.Create;

View File

@ -3465,7 +3465,7 @@ begin
// AncestorScope can be derived from DirectAncestor
// CanonicalClassOf is autogenerated
CanonicalClassOf:=Scope.CanonicalClassOf;
if aClass.ObjKind=okClass then
if aClass.ObjKind in ([okClass]+okAllHelpers) then
begin
if CanonicalClassOf=nil then
RaiseMsg(20180217143821,aClass);

View File

@ -31,14 +31,15 @@ uses
Classes, SysUtils, PScanner, fpjson;
const // Messages
nUsingPath = 104; sUsingPath = 'Using %s: "%s"';
nFolderNotFound = 105; sFolderNotFound = '%s not found: %s';
nIncludeSearch = 201; sIncludeSearch = 'Include file search: %s';
nUnitSearch = 202; sUnitSearch = 'Unitsearch: %s';
nSearchingFileFound = 203; sSearchingFileFound = 'Searching file: %s... found';
nSearchingFileNotFound = 204; sSearchingFileNotFound = 'Searching file: %s... not found';
nDuplicateFileFound = 205; sDuplicateFileFound = 'Duplicate file found: "%s" and "%s"';
nCustomJSFileNotFound = 206; sCustomJSFileNotFound = 'custom JS file not found: "%s"';
nUsingPath = 104; sUsingPath = 'Using %s: "%s"';
nFolderNotFound = 105; sFolderNotFound = '%s not found: %s';
Type
// Forward definitions

View File

@ -13,8 +13,13 @@
**********************************************************************
Abstract:
Extends the FCL Pascal use analyzer for the language subset of pas2js.
Abstract:
Extends the FCL Pascal use analyzer for the language subset of pas2js.
Works:
- Array of Const marks function System.VarRecs()
- TPascalDescendantOfExt.Create marks class method NewInstance
}
unit Pas2jsUseAnalyzer;
@ -35,6 +40,7 @@ type
TPas2JSAnalyzer = class(TPasAnalyzer)
public
procedure UseExpr(El: TPasExpr); override;
procedure UseConstructor(Proc: TPasConstructor; PosEl: TPasElement); virtual;
end;
implementation
@ -86,11 +92,35 @@ begin
Ref:=TResolvedReference(El.CustomData);
Decl:=Ref.Declaration;
if Decl is TPasProcedure then
CheckArgs(TPasProcedure(Decl).ProcType.Args)
begin
CheckArgs(TPasProcedure(Decl).ProcType.Args);
if Decl.ClassType=TPasConstructor then
UseConstructor(TPasConstructor(Decl),El);
end
else if Decl.ClassType=TPasProperty then
CheckArgs(Resolver.GetPasPropertyArgs(TPasProperty(Decl)));
end;
end;
procedure TPas2JSAnalyzer.UseConstructor(Proc: TPasConstructor;
PosEl: TPasElement);
var
ClassScope: TPas2JSClassScope;
begin
if Proc.Parent.ClassType=TPasClassType then
begin
ClassScope:=TPasClassType(Proc.Parent).CustomData as TPas2JSClassScope;
repeat
if ClassScope.NewInstanceFunction<>nil then
begin
UseProcedure(ClassScope.NewInstanceFunction);
break;
end;
ClassScope:=ClassScope.AncestorScope as TPas2JSClassScope;
until ClassScope=nil;
end;
if PosEl=nil then ;
end;
end.

View File

@ -461,6 +461,7 @@ type
Procedure TestRecord_Const;
Procedure TestRecord_TypecastFail;
Procedure TestRecord_InFunction;
Procedure TestRecord_AnonymousFail;
// ToDo: RTTI of local record
// ToDo: pcu local record, name clash and rtti
@ -680,6 +681,7 @@ type
Procedure TestTypeHelper_ClassProperty;
Procedure TestTypeHelper_ClassProperty_Array;
Procedure TestTypeHelper_ClassMethod;
Procedure TestTypeHelper_ExtClassMethodFail;
Procedure TestTypeHelper_Constructor;
Procedure TestTypeHelper_Word;
Procedure TestTypeHelper_Double;
@ -3720,6 +3722,11 @@ begin
' // end',
' s = ''end'';',
' s = "end";',
' s = "foo\"bar";',
' s = ''a\''b'';',
' s = `${expr}\`-"-''-`;',
' s = `multi',
'line`;',
' end;',
'end;',
'procedure Fly;',
@ -3740,6 +3747,11 @@ begin
' // end',
' s = ''end'';',
' s = "end";',
' s = "foo\"bar";',
' s = ''a\''b'';',
' s = `${expr}\`-"-''-`;',
' s = `multi',
'line`;',
' return Result;',
'};',
'this.Fly = function () {',
@ -10600,6 +10612,18 @@ begin
'']));
end;
procedure TTestModule.TestRecord_AnonymousFail;
begin
StartProgram(false);
Add([
'var',
' r: record x: word end;',
'begin']);
SetExpectedPasResolverError('not yet implemented: :TPasRecordType [20190408224556] anonymous record type',
nNotYetImplemented);
ConvertProgram;
end;
procedure TTestModule.TestAdvRecord_Function;
begin
StartProgram(false);
@ -11207,6 +11231,7 @@ begin
'var r: TPoint;',
'begin',
' r:=TPoint.Create(1,2);',
' with TPoint do r:=Create(1,2);',
' r.Create(3);',
' r:=r.Create(4);',
'']);
@ -11233,7 +11258,9 @@ begin
'this.r = $mod.TPoint.$new();',
'']),
LinesToStr([ // $mod.$main
'$mod.r.$assign($mod.TPoint.$create("Create", [1, 2]));',
'$mod.r.$assign($mod.TPoint.$new().Create(1, 2));',
'var $with1 = $mod.TPoint;',
'$mod.r.$assign($with1.$new().Create(1, 2));',
'$mod.r.Create(3, -1);',
'$mod.r.$assign($mod.r.Create(4, -1));',
'']));
@ -16018,6 +16045,7 @@ begin
Add(' A: texta;');
Add('begin');
Add(' a:=texta.new;');
Add(' a:=texta(texta.new);');
Add(' a:=texta.new();');
Add(' a:=texta.new(1);');
Add(' with texta do begin');
@ -16036,6 +16064,7 @@ begin
LinesToStr([ // $mod.$main
'$mod.A = new ExtA();',
'$mod.A = new ExtA();',
'$mod.A = new ExtA();',
'$mod.A = new ExtA(1,2);',
'$mod.A = new ExtA();',
'$mod.A = new ExtA();',
@ -21197,12 +21226,15 @@ begin
Add([
'{$modeswitch externalclass}',
'type',
' TFly = function(w: word): word of object;',
' TExtA = class external name ''ExtObj''',
' procedure Run(w: word = 10);',
' end;',
' THelper = class helper for TExtA',
' function Foo(w: word = 1): word;',
' function Fly(w: word = 2): word; external name ''Fly'';',
' end;',
'var p: TFly;',
'function THelper.foo(w: word): word;',
'begin',
' Run;',
@ -21214,22 +21246,32 @@ begin
' Self.Foo;',
' Self.Foo();',
' Self.Foo(13);',
' Fly;',
' Fly();',
' with Self do begin',
' Foo;',
' Foo();',
' Foo(14);',
' Fly;',
' Fly();',
' end;',
' p:=@Fly;',
'end;',
'var Obj: TExtA;',
'begin',
' obj.Foo;',
' obj.Foo();',
' obj.Foo(21);',
' obj.Fly;',
' obj.Fly();',
' with obj do begin',
' Foo;',
' Foo();',
' Foo(22);',
' Fly;',
' Fly();',
' end;',
' p:=@obj.Fly;',
'']);
ConvertProgram;
CheckSource('TestExtClassHelper_Method_Call',
@ -21246,22 +21288,33 @@ begin
' $mod.THelper.Foo.call(this, 1);',
' $mod.THelper.Foo.call(this, 1);',
' $mod.THelper.Foo.call(this, 13);',
' this.Fly(2);',
' this.Fly(2);',
' $mod.THelper.Foo.call(this, 1);',
' $mod.THelper.Foo.call(this, 1);',
' $mod.THelper.Foo.call(this, 14);',
' this.Fly(2);',
' this.Fly(2);',
' $mod.p = rtl.createCallback(this, "Fly");',
' return Result;',
' };',
'});',
'this.p = null;',
'this.Obj = null;',
'']),
LinesToStr([ // $mod.$main
'$mod.THelper.Foo.call($mod.Obj, 1);',
'$mod.THelper.Foo.call($mod.Obj, 1);',
'$mod.THelper.Foo.call($mod.Obj, 21);',
'$mod.Obj.Fly(2);',
'$mod.Obj.Fly(2);',
'var $with1 = $mod.Obj;',
'$mod.THelper.Foo.call($with1, 1);',
'$mod.THelper.Foo.call($with1, 1);',
'$mod.THelper.Foo.call($with1, 22);',
'$with1.Fly(2);',
'$with1.Fly(2);',
'$mod.p = rtl.createCallback($mod.Obj, "Fly");',
'']));
end;
@ -21520,7 +21573,7 @@ begin
'rtl.createHelper($mod, "THelper", null, function () {',
' this.NewHlp = function (w) {',
' this.Create(2);',
' $mod.TRec.$create("Create", [3]);',
' $mod.TRec.$new().Create(3);',
' $mod.THelper.NewHlp.call(this, 4);',
' $mod.THelper.$new("NewHlp", [5]);',
' return this;',
@ -23022,6 +23075,23 @@ begin
'']));
end;
procedure TTestModule.TestTypeHelper_ExtClassMethodFail;
begin
StartProgram(false);
Add([
'{$modeswitch typehelpers}',
'type',
' THelper = type helper for word',
' procedure Run; external name ''Run'';',
' end;',
'var w: word;',
'begin',
' w.Run;',
'']);
SetExpectedPasResolverError('Not supported: external method in type helper',nNotSupportedX);
ConvertProgram;
end;
procedure TTestModule.TestTypeHelper_Constructor;
begin
StartProgram(false);

View File

@ -76,6 +76,7 @@ type
procedure TestWPO_Class_OmitPropertyGetter2;
procedure TestWPO_Class_OmitPropertySetter1;
procedure TestWPO_Class_OmitPropertySetter2;
procedure TestWPO_Class_KeepNewInstance;
procedure TestWPO_CallInherited;
procedure TestWPO_UseUnit;
procedure TestWPO_ArrayOfConst_Use;
@ -724,6 +725,56 @@ begin
'']));
end;
procedure TTestOptimizations.TestWPO_Class_KeepNewInstance;
begin
StartProgram(false);
Add([
'{$modeswitch externalclass}',
'type',
' TExt = class external name ''Object''',
' end;',
' TBird = class(TExt)',
' protected',
' class function NewInstance(fnname: string; const paramarray): TBird; virtual;',
' public',
' constructor Create;',
' end;',
'class function TBird.NewInstance(fnname: string; const paramarray): TBird;',
'begin',
' asm',
' Result = Object.create();',
' end;',
'end;',
'constructor TBird.Create;',
'begin',
' inherited;',
'end;',
'begin',
' TBird.Create;',
'']);
ConvertProgram;
CheckSource('TestWPO_Class_KeepNewInstance',
LinesToStr([
'rtl.createClassExt($mod, "TBird", Object, "NewInstance", function () {',
' this.$init = function () {',
' };',
' this.$final = function () {',
' };',
' this.NewInstance = function (fnname, paramarray) {',
' var Result = null;',
' Result = Object.create();',
' return Result;',
' };',
' this.Create = function () {',
' return this;',
' };',
'});',
'']),
LinesToStr([
'$mod.TBird.$create("Create");',
'']));
end;
procedure TTestOptimizations.TestWPO_CallInherited;
begin
StartProgram(false);

View File

@ -1,10 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="11"/>
<Version Value="12"/>
<General>
<Flags>
<MainUnitHasCreateFormStatements Value="False"/>
<MainUnitHasTitleStatement Value="False"/>
<MainUnitHasScaledStatement Value="False"/>
<CompatibilityMode Value="True"/>
</Flags>
<SessionStorage Value="InProjectDir"/>
<MainUnit Value="0"/>
<Title Value="Pascal to Javascript converter tests"/>
<UseAppBundle Value="False"/>
<ResourceType Value="res"/>
@ -13,7 +18,7 @@
<EnableI18N LFM="False"/>
</i18n>
<BuildModes Count="1">
<Item1 Name="Default" Default="True"/>
<Item1 Name="default" Default="True"/>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
@ -97,7 +102,7 @@
</Target>
<SearchPaths>
<IncludeFiles Value="$(ProjOutDir)"/>
<OtherUnitFiles Value="../src;../../fcl-js/src;../../fcl-passrc/src;../../pastojs/tests"/>
<OtherUnitFiles Value="../src;../../fcl-js/src;../../fcl-json/src;../../fcl-passrc/src;../../pastojs/tests"/>
<UnitOutputDirectory Value="lib"/>
</SearchPaths>
<CodeGeneration>

View File

@ -37,7 +37,6 @@ begin
DefaultRunAllTests:=True;
Application := TMyTestRunner.Create(nil);
Application.Initialize;
Application.Title:='Pascal to Javascript converter tests';
Application.Run;
Application.Free;
end.

View File

@ -1001,12 +1001,13 @@ begin
Result:=TIDLDictionaryDefinition(Context.Add(aParent,TIDLDictionaryDefinition,Name));
Result.ParentName:=ParentName;
GetToken;
Repeat
While (CurrentToken<>tkCurlyBraceClose) do
begin
ParseDictionaryMember(Result.Members);
CheckCurrentTokens([tkSemicolon,tkCurlyBraceClose]);
if (CurrentToken=tkSemicolon) then
GetToken;
Until (CurrentToken=tkCurlyBraceClose);
end;
end;
function TWebIDLParser.ParseSequenceTypeDef(aParent : TIDLBaseObject): TIDLSequenceTypeDefDefinition;

View File

@ -1103,7 +1103,7 @@ var rtl = {
if (a<0) a += rtl.hiInt;
if (b<=0) return a;
if (b>54) return 0;
var r = a * (2**b);
var r = a * Math.pow(2,b);
if (r <= rtl.hiInt) return r;
return r % rtl.hiInt;
},

View File

@ -228,6 +228,9 @@ Put + after a boolean switch option to enable it, - to disable it
-vm&lt;x&gt;,&lt;y&gt;: Do not show messages numbered &lt;x&gt; and &lt;y&gt;.
-? : Show this help
-h : Show this help
Environment variable PAS2JS_OPTS is parsed after default config
and before command line parameters.
</pre>
</div>
@ -1867,8 +1870,9 @@ function(){
<li>A <b>record helper</b> can "extend" a record type. In $mode delphi a
record helper can extend other types as well, see <i>type helper</i></li>
<li>A <b>type helper</b> can extend all base types like integer, string,
char, boolean, double, currency, and some user types like enumeration,
set, range and array types. It cannot extend interfaces or helpers.<br>
char, boolean, double, currency, and user types like enumeration,
set, range, array, class, record and interface types.
It cannot extend helpers and procedural types.<br>
Type helpers are enabled by default in <i>$mode delphi</i> and disabled in <i>$mode objfpc</i>.
You can enable them with <b>{$modeswitch typehelpers}</b>.
</li>
@ -1929,6 +1933,8 @@ function(){
<li><i>with value do ;</i> : uses a temporary variable. Delphi/FPC do not support it.</li>
</ul>
</li>
<li>A method with <i>external name</i> modifier is treated as an external
method of the helped type.</li>
</ul>
</div>

View File

@ -66,7 +66,6 @@ function TPas2JSWebcompiler.DoWriteJSFile(const DestFilename: String; aWriter: T
Var
S : String;
T : String;
begin
// Writeln('aWriter',AWriter.BufferLength,', array size ',Length(AWriter.Buffer));

View File

@ -3,7 +3,7 @@ unit webfilecache;
{$mode objfpc}
// Enable this to write lots of debugging info to the browser console.
{ $DEFINE VERBOSEWEBCACHE}
{$DEFINE VERBOSEWEBCACHE}
interface
@ -94,8 +94,8 @@ type
function CreateResolver: TPas2jsFSResolver; override;
function FileExists(const aFileName: String): Boolean; override;
function FindCustomJSFileName(const aFilename: string): String; override;
function FindIncludeFileName(const aFilename: string): String; override;
function FindUnitFileName(const aUnitname, InFilename: string; out IsForeign: boolean): String; override;
function FindIncludeFileName(const aFilename, ModuleDir: string): String; override;
function FindUnitFileName(const aUnitname, InFilename, ModuleDir: string; out IsForeign: boolean): String; override;
function FindUnitJSFileName(const aUnitFilename: string): String; override;
function LoadFile(Filename: string; Binary: boolean=false): TPas2jsFile; override;
procedure SaveToFile(ms: TFPJSStream; Filename: string); override;
@ -330,10 +330,11 @@ begin
{$ENDIF}
end;
function TPas2jsWebFS.FindIncludeFileName(const aFilename: string): String;
function TPas2jsWebFS.FindIncludeFileName(const aFilename, ModuleDir: string
): String;
begin
{$IFDEF VERBOSEWEBCACHE}
Writeln(ClassName,': FindIncludeFileName(',aFileName,')');
Writeln(ClassName,': FindIncludeFileName(',aFileName,',',ModuleDir,')');
{$ENDIF}
Result:=NormalizeFileName(aFileName);
If not FCache.HasFile(Result) then
@ -372,10 +373,10 @@ begin
Result:=TPas2jsWebResolver.Create(Self);
end;
function TPas2jsWebFS.FindUnitFileName(const aUnitname, InFilename: string; out IsForeign: boolean): String;
function TPas2jsWebFS.FindUnitFileName(const aUnitname, InFilename, ModuleDir: string; out IsForeign: boolean): String;
begin
{$IFDEF VERBOSEWEBCACHE}
Writeln(ClassName,': FindUnitFileName(',aUnitName,')');
Writeln(ClassName,': FindUnitFileName(',aUnitName,',',InFilename,',',ModuleDir,')');
{$ENDIF}
Result:=NormalizeFileName(aUnitName+'.pas');
isForeign:=False;
@ -493,7 +494,8 @@ begin
end;
end;
Function TPas2jsWebFS.LoadFiles(aList: TStrings; OnLoaded: TLoadFileEvent): Integer;
function TPas2jsWebFS.LoadFiles(aList: TStrings; OnLoaded: TLoadFileEvent
): Integer;
Var
i: Integer;
@ -505,7 +507,8 @@ begin
Inc(Result);
end;
function TPas2jsWebFS.LoadFiles(aList: array of String; OnLoaded: TLoadFileEvent): Integer;
function TPas2jsWebFS.LoadFiles(aList: array of String; OnLoaded: TLoadFileEvent
): integer;
Var
i: Integer;