mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-09-15 03:32:31 +02:00
* QueryOptions for disconnected mode and autoapplyupdates
git-svn-id: trunk@29088 -
This commit is contained in:
parent
475a9e1617
commit
a53fca1b67
@ -504,7 +504,6 @@ type
|
|||||||
procedure SetIndexFieldNames(const AValue: String);
|
procedure SetIndexFieldNames(const AValue: String);
|
||||||
procedure SetIndexName(AValue: String);
|
procedure SetIndexName(AValue: String);
|
||||||
procedure SetMaxIndexesCount(const AValue: Integer);
|
procedure SetMaxIndexesCount(const AValue: Integer);
|
||||||
procedure SetPacketRecords(aValue : integer);
|
|
||||||
procedure SetBufUniDirectional(const AValue: boolean);
|
procedure SetBufUniDirectional(const AValue: boolean);
|
||||||
// indexes handling
|
// indexes handling
|
||||||
procedure InitDefaultIndexes;
|
procedure InitDefaultIndexes;
|
||||||
@ -513,6 +512,7 @@ type
|
|||||||
procedure RemoveRecordFromIndexes(const ABookmark : TBufBookmark);
|
procedure RemoveRecordFromIndexes(const ABookmark : TBufBookmark);
|
||||||
protected
|
protected
|
||||||
// abstract & virtual methods of TDataset
|
// abstract & virtual methods of TDataset
|
||||||
|
procedure SetPacketRecords(aValue : integer); virtual;
|
||||||
procedure UpdateIndexDefs; override;
|
procedure UpdateIndexDefs; override;
|
||||||
procedure SetRecNo(Value: Longint); override;
|
procedure SetRecNo(Value: Longint); override;
|
||||||
function GetRecNo: Longint; override;
|
function GetRecNo: Longint; override;
|
||||||
|
@ -379,15 +379,27 @@ begin
|
|||||||
DatabaseError(SErrNoDatabaseAvailable,Self)
|
DatabaseError(SErrNoDatabaseAvailable,Self)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
Function TDBTransaction.AllowClose(DS : TDBDataset) : Boolean;
|
||||||
|
|
||||||
|
begin
|
||||||
|
Result:=Assigned(DS);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TDBTransaction.CloseDataSets;
|
procedure TDBTransaction.CloseDataSets;
|
||||||
|
|
||||||
Var I : longint;
|
Var
|
||||||
|
I : longint;
|
||||||
|
DS : TDBDataset;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
If Assigned(FDatasets) then
|
If Assigned(FDatasets) then
|
||||||
begin
|
begin
|
||||||
For I:=FDatasets.Count-1 downto 0 do
|
For I:=FDatasets.Count-1 downto 0 do
|
||||||
TDBDataset(FDatasets[i]).Close;
|
begin
|
||||||
|
DS:=TDBDataset(FDatasets[i]);
|
||||||
|
If AllowClose(DS) then
|
||||||
|
DS.Close;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
@ -1607,7 +1607,7 @@ type
|
|||||||
function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; virtual;
|
function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; virtual;
|
||||||
procedure CursorPosChanged;
|
procedure CursorPosChanged;
|
||||||
procedure DataConvert(aField: TField; aSource, aDest: Pointer; aToNative: Boolean); virtual;
|
procedure DataConvert(aField: TField; aSource, aDest: Pointer; aToNative: Boolean); virtual;
|
||||||
procedure Delete;
|
procedure Delete; virtual;
|
||||||
procedure DisableControls;
|
procedure DisableControls;
|
||||||
procedure Edit;
|
procedure Edit;
|
||||||
procedure EnableControls;
|
procedure EnableControls;
|
||||||
@ -1878,6 +1878,7 @@ type
|
|||||||
procedure RemoveDataSets;
|
procedure RemoveDataSets;
|
||||||
procedure SetActive(Value : boolean);
|
procedure SetActive(Value : boolean);
|
||||||
Protected
|
Protected
|
||||||
|
Function AllowClose(DS: TDBDataset): Boolean; virtual;
|
||||||
Procedure SetDatabase (Value : TDatabase); virtual;
|
Procedure SetDatabase (Value : TDatabase); virtual;
|
||||||
procedure CloseTrans;
|
procedure CloseTrans;
|
||||||
procedure openTrans;
|
procedure openTrans;
|
||||||
|
@ -112,6 +112,7 @@ Resourcestring
|
|||||||
SErrNoFieldsDefined = 'Can not create a dataset when there are no fielddefinitions or fields defined';
|
SErrNoFieldsDefined = 'Can not create a dataset when there are no fielddefinitions or fields defined';
|
||||||
SErrApplyUpdBeforeRefresh= 'Must apply updates before refreshing data';
|
SErrApplyUpdBeforeRefresh= 'Must apply updates before refreshing data';
|
||||||
SErrNoDataset = 'Missing (compatible) underlying dataset, can not open';
|
SErrNoDataset = 'Missing (compatible) underlying dataset, can not open';
|
||||||
|
SErrDisconnectedPacketRecords = 'For disconnected TSQLQuery instances, packetrecords must be -1';
|
||||||
|
|
||||||
Implementation
|
Implementation
|
||||||
|
|
||||||
|
@ -24,8 +24,9 @@ uses SysUtils, Classes, DB, bufdataset, sqlscript;
|
|||||||
|
|
||||||
type
|
type
|
||||||
TSchemaType = (stNoSchema, stTables, stSysTables, stProcedures, stColumns, stProcedureParams, stIndexes, stPackages, stSchemata);
|
TSchemaType = (stNoSchema, stTables, stSysTables, stProcedures, stColumns, stProcedureParams, stIndexes, stPackages, stSchemata);
|
||||||
TConnOption = (sqSupportParams, sqSupportEmptyDatabaseName, sqEscapeSlash, sqEscapeRepeat);
|
TConnOption = (sqSupportParams, sqSupportEmptyDatabaseName, sqEscapeSlash, sqEscapeRepeat, sqImplicitTransaction);
|
||||||
TConnOptions= set of TConnOption;
|
TConnOptions= set of TConnOption;
|
||||||
|
|
||||||
TConnInfoType=(citAll=-1, citServerType, citServerVersion, citServerVersionString, citClientName, citClientVersion);
|
TConnInfoType=(citAll=-1, citServerType, citServerVersion, citServerVersionString, citClientName, citClientVersion);
|
||||||
TStatementType = (stUnknown, stSelect, stInsert, stUpdate, stDelete,
|
TStatementType = (stUnknown, stSelect, stInsert, stUpdate, stDelete,
|
||||||
stDDL, stGetSegment, stPutSegment, stExecProcedure,
|
stDDL, stGetSegment, stPutSegment, stExecProcedure,
|
||||||
@ -56,6 +57,9 @@ type
|
|||||||
TDBEventTypes = set of TDBEventType;
|
TDBEventTypes = set of TDBEventType;
|
||||||
TDBLogNotifyEvent = Procedure (Sender : TSQLConnection; EventType : TDBEventType; Const Msg : String) of object;
|
TDBLogNotifyEvent = Procedure (Sender : TSQLConnection; EventType : TDBEventType; Const Msg : String) of object;
|
||||||
|
|
||||||
|
TSQLQueryOption = (sqoDisconnected, sqoAutoApplyUpdates);
|
||||||
|
TSQLQueryOptions = Set of TSQLQueryOption;
|
||||||
|
|
||||||
TSQLHandle = Class(TObject)
|
TSQLHandle = Class(TObject)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@ -153,11 +157,13 @@ type
|
|||||||
FStatements : TFPList;
|
FStatements : TFPList;
|
||||||
FLogEvents: TDBEventTypes;
|
FLogEvents: TDBEventTypes;
|
||||||
FOnLog: TDBLogNotifyEvent;
|
FOnLog: TDBLogNotifyEvent;
|
||||||
|
FInternalTransaction : TSQLTransaction;
|
||||||
function GetPort: cardinal;
|
function GetPort: cardinal;
|
||||||
procedure SetPort(const AValue: cardinal);
|
procedure SetPort(const AValue: cardinal);
|
||||||
protected
|
protected
|
||||||
FConnOptions : TConnOptions;
|
FConnOptions : TConnOptions;
|
||||||
FSQLFormatSettings : TFormatSettings;
|
FSQLFormatSettings : TFormatSettings;
|
||||||
|
|
||||||
// Updating of DB records is moved out of TSQLQuery.
|
// Updating of DB records is moved out of TSQLQuery.
|
||||||
// It is done here, so descendents can override it and implement DB-specific.
|
// It is done here, so descendents can override it and implement DB-specific.
|
||||||
// One day, this may be factored out to a TSQLResolver class.
|
// One day, this may be factored out to a TSQLResolver class.
|
||||||
@ -170,7 +176,6 @@ type
|
|||||||
procedure ApplyFieldUpdate(C : TSQLCursor; P: TSQLDBParam; F: TField; UseOldValue: Boolean); virtual;
|
procedure ApplyFieldUpdate(C : TSQLCursor; P: TSQLDBParam; F: TField; UseOldValue: Boolean); virtual;
|
||||||
// This is the call that updates a record, it used to be in TSQLQuery.
|
// This is the call that updates a record, it used to be in TSQLQuery.
|
||||||
procedure ApplyRecUpdate(Query : TCustomSQLQuery; UpdateKind : TUpdateKind); virtual;
|
procedure ApplyRecUpdate(Query : TCustomSQLQuery; UpdateKind : TUpdateKind); virtual;
|
||||||
//
|
|
||||||
procedure GetDBInfo(const ASchemaType : TSchemaType; const ASchemaObjectName, AReturnField : string; AList: TStrings);
|
procedure GetDBInfo(const ASchemaType : TSchemaType; const ASchemaObjectName, AReturnField : string; AList: TStrings);
|
||||||
procedure SetTransaction(Value : TSQLTransaction); virtual;
|
procedure SetTransaction(Value : TSQLTransaction); virtual;
|
||||||
procedure DoInternalConnect; override;
|
procedure DoInternalConnect; override;
|
||||||
@ -182,7 +187,6 @@ type
|
|||||||
Procedure Log(EventType : TDBEventType; Const Msg : String); virtual;
|
Procedure Log(EventType : TDBEventType; Const Msg : String); virtual;
|
||||||
Procedure RegisterStatement(S : TCustomSQLStatement);
|
Procedure RegisterStatement(S : TCustomSQLStatement);
|
||||||
Procedure UnRegisterStatement(S : TCustomSQLStatement);
|
Procedure UnRegisterStatement(S : TCustomSQLStatement);
|
||||||
|
|
||||||
Function AllocateCursorHandle : TSQLCursor; virtual; abstract;
|
Function AllocateCursorHandle : TSQLCursor; virtual; abstract;
|
||||||
Procedure DeAllocateCursorHandle(var cursor : TSQLCursor); virtual; abstract;
|
Procedure DeAllocateCursorHandle(var cursor : TSQLCursor); virtual; abstract;
|
||||||
function StrToStatementType(s : string) : TStatementType; virtual;
|
function StrToStatementType(s : string) : TStatementType; virtual;
|
||||||
@ -258,6 +262,7 @@ type
|
|||||||
procedure SetParams(const AValue: TStringList);
|
procedure SetParams(const AValue: TStringList);
|
||||||
procedure SetSQLConnection(AValue: TSQLConnection);
|
procedure SetSQLConnection(AValue: TSQLConnection);
|
||||||
protected
|
protected
|
||||||
|
Function AllowClose(DS: TDBDataset): Boolean; override;
|
||||||
function GetHandle : Pointer; virtual;
|
function GetHandle : Pointer; virtual;
|
||||||
Procedure SetDatabase (Value : TDatabase); override;
|
Procedure SetDatabase (Value : TDatabase); override;
|
||||||
Function LogEvent(EventType : TDBEventType) : Boolean;
|
Function LogEvent(EventType : TDBEventType) : Boolean;
|
||||||
@ -279,6 +284,8 @@ type
|
|||||||
property Params : TStringList read FParams write SetParams;
|
property Params : TStringList read FParams write SetParams;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{ TCustomSQLStatement }
|
{ TCustomSQLStatement }
|
||||||
|
|
||||||
TCustomSQLStatement = Class(TComponent)
|
TCustomSQLStatement = Class(TComponent)
|
||||||
@ -352,6 +359,7 @@ type
|
|||||||
|
|
||||||
TCustomSQLQuery = class (TCustomBufDataset)
|
TCustomSQLQuery = class (TCustomBufDataset)
|
||||||
private
|
private
|
||||||
|
FQueryOptions: TSQLQueryOptions;
|
||||||
FSchemaType : TSchemaType;
|
FSchemaType : TSchemaType;
|
||||||
FUpdateable : boolean;
|
FUpdateable : boolean;
|
||||||
FTableName : string;
|
FTableName : string;
|
||||||
@ -387,6 +395,7 @@ type
|
|||||||
function GetSQLTransaction: TSQLTransaction;
|
function GetSQLTransaction: TSQLTransaction;
|
||||||
function GetStatementType : TStatementType;
|
function GetStatementType : TStatementType;
|
||||||
procedure SetParamCheck(AValue: Boolean);
|
procedure SetParamCheck(AValue: Boolean);
|
||||||
|
procedure SetQueryOptions(AValue: TSQLQueryOptions);
|
||||||
procedure SetSQLConnection(AValue: TSQLConnection);
|
procedure SetSQLConnection(AValue: TSQLConnection);
|
||||||
procedure SetSQLTransaction(AValue: TSQLTransaction);
|
procedure SetSQLTransaction(AValue: TSQLTransaction);
|
||||||
procedure SetUpdateSQL(const AValue: TStringlist);
|
procedure SetUpdateSQL(const AValue: TStringlist);
|
||||||
@ -402,6 +411,7 @@ type
|
|||||||
procedure ApplyFilter;
|
procedure ApplyFilter;
|
||||||
Function AddFilter(SQLstr : string) : string;
|
Function AddFilter(SQLstr : string) : string;
|
||||||
protected
|
protected
|
||||||
|
procedure SetPacketRecords(aValue : integer); override;
|
||||||
Function Cursor : TSQLCursor;
|
Function Cursor : TSQLCursor;
|
||||||
Function LogEvent(EventType : TDBEventType) : Boolean;
|
Function LogEvent(EventType : TDBEventType) : Boolean;
|
||||||
Procedure Log(EventType : TDBEventType; Const Msg : String); virtual;
|
Procedure Log(EventType : TDBEventType; Const Msg : String); virtual;
|
||||||
@ -439,6 +449,8 @@ type
|
|||||||
procedure Prepare; virtual;
|
procedure Prepare; virtual;
|
||||||
procedure UnPrepare; virtual;
|
procedure UnPrepare; virtual;
|
||||||
procedure ExecSQL; virtual;
|
procedure ExecSQL; virtual;
|
||||||
|
Procedure Post; override;
|
||||||
|
Procedure Delete; override;
|
||||||
procedure SetSchemaInfo( ASchemaType : TSchemaType; ASchemaObjectName, ASchemaPattern : string); virtual;
|
procedure SetSchemaInfo( ASchemaType : TSchemaType; ASchemaObjectName, ASchemaPattern : string); virtual;
|
||||||
function RowsAffected: TRowsCount; virtual;
|
function RowsAffected: TRowsCount; virtual;
|
||||||
function ParamByName(Const AParamName : String) : TParam;
|
function ParamByName(Const AParamName : String) : TParam;
|
||||||
@ -475,6 +487,7 @@ type
|
|||||||
property AutoCalcFields;
|
property AutoCalcFields;
|
||||||
property Database;
|
property Database;
|
||||||
// protected
|
// protected
|
||||||
|
Property QueryOptions : TSQLQueryOptions Read FQueryOptions Write SetQueryOptions;
|
||||||
property SchemaType : TSchemaType read FSchemaType default stNoSchema;
|
property SchemaType : TSchemaType read FSchemaType default stNoSchema;
|
||||||
property Transaction;
|
property Transaction;
|
||||||
property SQL : TStringlist read GetSQL write SetSQL;
|
property SQL : TStringlist read GetSQL write SetSQL;
|
||||||
@ -530,6 +543,7 @@ type
|
|||||||
Property OnPostError;
|
Property OnPostError;
|
||||||
|
|
||||||
// property SchemaInfo default stNoSchema;
|
// property SchemaInfo default stNoSchema;
|
||||||
|
Property QueryOptions;
|
||||||
property Database;
|
property Database;
|
||||||
property Transaction;
|
property Transaction;
|
||||||
property ReadOnly;
|
property ReadOnly;
|
||||||
@ -729,6 +743,9 @@ begin
|
|||||||
SQLState := ASQLState;
|
SQLState := ASQLState;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
Type
|
||||||
|
TInternalTransaction = Class(TSQLTransaction);
|
||||||
|
|
||||||
{ TCustomSQLStatement }
|
{ TCustomSQLStatement }
|
||||||
|
|
||||||
procedure TCustomSQLStatement.OnChangeSQL(Sender: TObject);
|
procedure TCustomSQLStatement.OnChangeSQL(Sender: TObject);
|
||||||
@ -784,7 +801,7 @@ begin
|
|||||||
FDataLink.DataSource:=AValue;
|
FDataLink.DataSource:=AValue;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TCustomSQLStatement.CopyParamsFromMaster(CopyBound : Boolean);
|
Procedure TCustomSQLStatement.CopyParamsFromMaster(CopyBound: Boolean);
|
||||||
begin
|
begin
|
||||||
if Assigned(DataSource) and Assigned(DataSource.Dataset) then
|
if Assigned(DataSource) and Assigned(DataSource.Dataset) then
|
||||||
FParams.CopyParamValuesFromDataset(DataSource.Dataset,CopyBound);
|
FParams.CopyParamValuesFromDataset(DataSource.Dataset,CopyBound);
|
||||||
@ -817,7 +834,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TCustomSQLStatement.DoExecute;
|
Procedure TCustomSQLStatement.DoExecute;
|
||||||
begin
|
begin
|
||||||
If (FParams.Count>0) and Assigned(DataSource) then
|
If (FParams.Count>0) and Assigned(DataSource) then
|
||||||
CopyParamsFromMaster(False);
|
CopyParamsFromMaster(False);
|
||||||
@ -826,27 +843,27 @@ begin
|
|||||||
Database.Execute(FCursor,Transaction, FParams);
|
Database.Execute(FCursor,Transaction, FParams);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCustomSQLStatement.GetPrepared: Boolean;
|
Function TCustomSQLStatement.GetPrepared: Boolean;
|
||||||
begin
|
begin
|
||||||
Result := Assigned(FCursor) and FCursor.FPrepared;
|
Result := Assigned(FCursor) and FCursor.FPrepared;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCustomSQLStatement.CreateDataLink: TDataLink;
|
Function TCustomSQLStatement.CreateDataLink: TDataLink;
|
||||||
begin
|
begin
|
||||||
Result:=TDataLink.Create;
|
Result:=TDataLink.Create;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCustomSQLStatement.CreateParams: TSQLDBParams;
|
Function TCustomSQLStatement.CreateParams: TSQLDBParams;
|
||||||
begin
|
begin
|
||||||
Result:=TSQLDBParams.Create(Nil);
|
Result:=TSQLDBParams.Create(Nil);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCustomSQLStatement.LogEvent(EventType: TDBEventType): Boolean;
|
Function TCustomSQLStatement.LogEvent(EventType: TDBEventType): Boolean;
|
||||||
begin
|
begin
|
||||||
Result:=Assigned(Database) and Database.LogEvent(EventType);
|
Result:=Assigned(Database) and Database.LogEvent(EventType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TCustomSQLStatement.Log(EventType: TDBEventType; const Msg: String);
|
Procedure TCustomSQLStatement.Log(EventType: TDBEventType; Const Msg: String);
|
||||||
Var
|
Var
|
||||||
M : String;
|
M : String;
|
||||||
|
|
||||||
@ -897,28 +914,29 @@ begin
|
|||||||
inherited Destroy;
|
inherited Destroy;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCustomSQLStatement.GetSchemaType: TSchemaType;
|
Function TCustomSQLStatement.GetSchemaType: TSchemaType;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
Result:=stNoSchema
|
Result:=stNoSchema
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCustomSQLStatement.GetSchemaObjectName: String;
|
Function TCustomSQLStatement.GetSchemaObjectName: String;
|
||||||
begin
|
begin
|
||||||
Result:='';
|
Result:='';
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCustomSQLStatement.GetSchemaPattern: String;
|
Function TCustomSQLStatement.GetSchemaPattern: String;
|
||||||
begin
|
begin
|
||||||
Result:='';
|
Result:='';
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCustomSQLStatement.IsSelectable: Boolean;
|
Function TCustomSQLStatement.IsSelectable: Boolean;
|
||||||
begin
|
begin
|
||||||
Result:=False;
|
Result:=False;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
procedure TCustomSQLStatement.GetStatementInfo(var ASQL: String; out Info: TSQLStatementInfo);
|
procedure TCustomSQLStatement.GetStatementInfo(var ASQL: String; out Info: TSQLStatementInfo);
|
||||||
|
|
||||||
begin
|
begin
|
||||||
@ -961,7 +979,7 @@ begin
|
|||||||
Database.PrepareStatement(FCursor,Transaction,FServerSQL,FParams);
|
Database.PrepareStatement(FCursor,Transaction,FServerSQL,FParams);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TCustomSQLStatement.Prepare;
|
Procedure TCustomSQLStatement.Prepare;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
if Prepared then exit;
|
if Prepared then exit;
|
||||||
@ -981,7 +999,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TCustomSQLStatement.Execute;
|
Procedure TCustomSQLStatement.Execute;
|
||||||
begin
|
begin
|
||||||
Prepare;
|
Prepare;
|
||||||
DoExecute;
|
DoExecute;
|
||||||
@ -1008,7 +1026,7 @@ begin
|
|||||||
Result:=Nil;
|
Result:=Nil;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TCustomSQLStatement.Unprepare;
|
Procedure TCustomSQLStatement.Unprepare;
|
||||||
begin
|
begin
|
||||||
// Some SQLConnections does not support statement [un]preparation, but they have allocated local cursor(s)
|
// Some SQLConnections does not support statement [un]preparation, but they have allocated local cursor(s)
|
||||||
// so let them do cleanup f.e. cancel pending queries and/or free resultset
|
// so let them do cleanup f.e. cancel pending queries and/or free resultset
|
||||||
@ -1017,7 +1035,7 @@ begin
|
|||||||
DoUnprepare;
|
DoUnprepare;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCustomSQLStatement.ParamByName(const AParamName: String): TParam;
|
function TCustomSQLStatement.ParamByName(Const AParamName: String): TParam;
|
||||||
begin
|
begin
|
||||||
Result:=FParams.ParamByName(AParamName);
|
Result:=FParams.ParamByName(AParamName);
|
||||||
end;
|
end;
|
||||||
@ -1072,6 +1090,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure TSQLConnection.UpdateIndexDefs(IndexDefs : TIndexDefs; TableName : string);
|
procedure TSQLConnection.UpdateIndexDefs(IndexDefs : TIndexDefs; TableName : string);
|
||||||
begin
|
begin
|
||||||
// Empty abstract
|
// Empty abstract
|
||||||
@ -1152,6 +1171,7 @@ begin
|
|||||||
result := StrToIntDef(Params.Values['Port'],0);
|
result := StrToIntDef(Params.Values['Port'],0);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure TSQLConnection.SetPort(const AValue: cardinal);
|
procedure TSQLConnection.SetPort(const AValue: cardinal);
|
||||||
begin
|
begin
|
||||||
if AValue<>0 then
|
if AValue<>0 then
|
||||||
@ -1440,12 +1460,12 @@ begin
|
|||||||
Result := nil;
|
Result := nil;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TSQLConnection.LogEvent(EventType: TDBEventType): Boolean;
|
Function TSQLConnection.LogEvent(EventType: TDBEventType): Boolean;
|
||||||
begin
|
begin
|
||||||
Result:=(Assigned(FOnLog) or Assigned(GlobalDBLogHook)) and (EventType in LogEvents);
|
Result:=(Assigned(FOnLog) or Assigned(GlobalDBLogHook)) and (EventType in LogEvents);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TSQLConnection.Log(EventType: TDBEventType; const Msg: String);
|
Procedure TSQLConnection.Log(EventType: TDBEventType; Const Msg: String);
|
||||||
|
|
||||||
Var
|
Var
|
||||||
M : String;
|
M : String;
|
||||||
@ -1466,18 +1486,19 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TSQLConnection.RegisterStatement(S: TCustomSQLStatement);
|
Procedure TSQLConnection.RegisterStatement(S: TCustomSQLStatement);
|
||||||
begin
|
begin
|
||||||
if FStatements.IndexOf(S)=-1 then
|
if FStatements.IndexOf(S)=-1 then
|
||||||
FStatements.Add(S);
|
FStatements.Add(S);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TSQLConnection.UnRegisterStatement(S: TCustomSQLStatement);
|
Procedure TSQLConnection.UnRegisterStatement(S: TCustomSQLStatement);
|
||||||
begin
|
begin
|
||||||
if Assigned(FStatements) then // Can be nil, when we are destroying and datasets are uncoupled.
|
if Assigned(FStatements) then // Can be nil, when we are destroying and datasets are uncoupled.
|
||||||
FStatements.Remove(S);
|
FStatements.Remove(S);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
function TSQLConnection.InitialiseUpdateStatement(Query : TCustomSQLQuery; var qry : TCustomSQLStatement): TCustomSQLStatement;
|
function TSQLConnection.InitialiseUpdateStatement(Query : TCustomSQLQuery; var qry : TCustomSQLStatement): TCustomSQLStatement;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
@ -1692,6 +1713,14 @@ begin
|
|||||||
Result := SQLConnection.GetTransactionHandle(FTrans);
|
Result := SQLConnection.GetTransactionHandle(FTrans);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
Function TSQLTransaction.AllowClose(DS: TDBDataset): Boolean;
|
||||||
|
begin
|
||||||
|
if (DS is TSQLQuery) then
|
||||||
|
Result:=not (sqoDisconnected in TSQLQuery(DS).QueryOptions)
|
||||||
|
else
|
||||||
|
Result:=Inherited AllowClose(DS);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TSQLTransaction.Commit;
|
procedure TSQLTransaction.Commit;
|
||||||
begin
|
begin
|
||||||
if Active then
|
if Active then
|
||||||
@ -1943,7 +1972,7 @@ begin
|
|||||||
inherited Destroy;
|
inherited Destroy;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCustomSQLQuery.ParamByName(const AParamName: String): TParam;
|
function TCustomSQLQuery.ParamByName(Const AParamName: String): TParam;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
Result:=Params.ParamByName(AParamName);
|
Result:=Params.ParamByName(AParamName);
|
||||||
@ -1955,7 +1984,7 @@ begin
|
|||||||
CheckInactive;
|
CheckInactive;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TCustomSQLQuery.SetTransaction(Value: TDBTransaction);
|
Procedure TCustomSQLQuery.SetTransaction(Value: TDBTransaction);
|
||||||
|
|
||||||
begin
|
begin
|
||||||
UnPrepare;
|
UnPrepare;
|
||||||
@ -1985,7 +2014,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCustomSQLQuery.IsPrepared: Boolean;
|
Function TCustomSQLQuery.IsPrepared: Boolean;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
if Assigned(Fstatement) then
|
if Assigned(Fstatement) then
|
||||||
@ -1994,7 +2023,7 @@ begin
|
|||||||
Result := False;
|
Result := False;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCustomSQLQuery.AddFilter(SQLstr: string): string;
|
Function TCustomSQLQuery.AddFilter(SQLstr: string): string;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
if (FWhereStartPos > 0) and (FWhereStopPos > 0) then
|
if (FWhereStartPos > 0) and (FWhereStopPos > 0) then
|
||||||
@ -2025,7 +2054,7 @@ begin
|
|||||||
First;
|
First;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TCustomSQLQuery.SetActive(Value: Boolean);
|
Procedure TCustomSQLQuery.SetActive(Value: Boolean);
|
||||||
|
|
||||||
begin
|
begin
|
||||||
inherited SetActive(Value);
|
inherited SetActive(Value);
|
||||||
@ -2056,6 +2085,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure TCustomSQLQuery.Prepare;
|
procedure TCustomSQLQuery.Prepare;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
@ -2114,7 +2144,15 @@ begin
|
|||||||
Result:=Transaction as TSQLTransaction;
|
Result:=Transaction as TSQLTransaction;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCustomSQLQuery.Cursor: TSQLCursor;
|
procedure TCustomSQLQuery.SetPacketRecords(aValue: integer);
|
||||||
|
begin
|
||||||
|
if (AValue=PacketRecords) then exit;
|
||||||
|
if (AValue<>-1) and (sqoDisconnected in QueryOptions) then
|
||||||
|
DatabaseError(SErrDisconnectedPacketRecords);
|
||||||
|
Inherited SetPacketRecords(aValue);
|
||||||
|
end;
|
||||||
|
|
||||||
|
Function TCustomSQLQuery.Cursor: TSQLCursor;
|
||||||
begin
|
begin
|
||||||
Result:=FStatement.Cursor;
|
Result:=FStatement.Cursor;
|
||||||
end;
|
end;
|
||||||
@ -2276,6 +2314,20 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
Procedure TCustomSQLQuery.Post;
|
||||||
|
begin
|
||||||
|
inherited Post;
|
||||||
|
If (sqoAutoApplyUpdates in QueryOptions) then
|
||||||
|
ApplyUpdates;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Procedure TCustomSQLQuery.Delete;
|
||||||
|
begin
|
||||||
|
inherited Delete;
|
||||||
|
If (sqoAutoApplyUpdates in QueryOptions) then
|
||||||
|
ApplyUpdates;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TCustomSQLQuery.SetReadOnly(AValue : Boolean);
|
procedure TCustomSQLQuery.SetReadOnly(AValue : Boolean);
|
||||||
|
|
||||||
begin
|
begin
|
||||||
@ -2364,12 +2416,12 @@ begin
|
|||||||
UnPrepareStatement(Cursor);
|
UnPrepareStatement(Cursor);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCustomSQLQuery.LogEvent(EventType: TDBEventType): Boolean;
|
Function TCustomSQLQuery.LogEvent(EventType: TDBEventType): Boolean;
|
||||||
begin
|
begin
|
||||||
Result:=Assigned(Database) and SQLConnection.LogEvent(EventType);
|
Result:=Assigned(Database) and SQLConnection.LogEvent(EventType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TCustomSQLQuery.Log(EventType: TDBEventType; const Msg: String);
|
Procedure TCustomSQLQuery.Log(EventType: TDBEventType; Const Msg: String);
|
||||||
|
|
||||||
Var
|
Var
|
||||||
M : String;
|
M : String;
|
||||||
@ -2403,6 +2455,15 @@ begin
|
|||||||
FStatement.ParamCheck:=AValue;
|
FStatement.ParamCheck:=AValue;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TCustomSQLQuery.SetQueryOptions(AValue: TSQLQueryOptions);
|
||||||
|
begin
|
||||||
|
if FQueryOptions=AValue then Exit;
|
||||||
|
CheckInactive;
|
||||||
|
FQueryOptions:=AValue;
|
||||||
|
if sqoDisconnected in FQueryOptions then
|
||||||
|
PacketRecords:=-1;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TCustomSQLQuery.SetSQLConnection(AValue: TSQLConnection);
|
procedure TCustomSQLQuery.SetSQLConnection(AValue: TSQLConnection);
|
||||||
begin
|
begin
|
||||||
Database:=AValue;
|
Database:=AValue;
|
||||||
@ -2428,7 +2489,7 @@ begin
|
|||||||
FStatement.Params.Assign(AValue);
|
FStatement.Params.Assign(AValue);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TCustomSQLQuery.SetDataSource(AValue: TDataSource);
|
Procedure TCustomSQLQuery.SetDataSource(AValue: TDataSource);
|
||||||
|
|
||||||
Var
|
Var
|
||||||
DS : TDataSource;
|
DS : TDataSource;
|
||||||
@ -2445,7 +2506,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCustomSQLQuery.GetDataSource: TDataSource;
|
Function TCustomSQLQuery.GetDataSource: TDataSource;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
If Assigned(FStatement) then
|
If Assigned(FStatement) then
|
||||||
|
@ -9,7 +9,7 @@ unit TestSQLDB;
|
|||||||
interface
|
interface
|
||||||
|
|
||||||
uses
|
uses
|
||||||
Classes, SysUtils, fpcunit, testregistry,
|
Classes, sqldb, SysUtils, fpcunit, testregistry,
|
||||||
db;
|
db;
|
||||||
|
|
||||||
type
|
type
|
||||||
@ -25,10 +25,20 @@ type
|
|||||||
{ TTestTSQLQuery }
|
{ TTestTSQLQuery }
|
||||||
|
|
||||||
TTestTSQLQuery = class(TSQLDBTestCase)
|
TTestTSQLQuery = class(TSQLDBTestCase)
|
||||||
|
procedure DoAfterPost(DataSet: TDataSet);
|
||||||
private
|
private
|
||||||
|
FMyQ: TSQLQuery;
|
||||||
|
Procedure Allow;
|
||||||
|
Procedure SetQueryOPtions;
|
||||||
|
Procedure TrySetPacketRecords;
|
||||||
published
|
published
|
||||||
procedure TestMasterDetail;
|
procedure TestMasterDetail;
|
||||||
procedure TestUpdateServerIndexDefs;
|
procedure TestUpdateServerIndexDefs;
|
||||||
|
Procedure TestDisconnected;
|
||||||
|
Procedure TestDisconnectedPacketRecords;
|
||||||
|
Procedure TestCheckSettingsOnlyWhenInactive;
|
||||||
|
Procedure TestAutoApplyUpdatesPost;
|
||||||
|
Procedure TestAutoApplyUpdatesDelete;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TTestTSQLConnection }
|
{ TTestTSQLConnection }
|
||||||
@ -50,11 +60,21 @@ type
|
|||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses sqldbtoolsunit, toolsunit, sqldb;
|
uses sqldbtoolsunit, toolsunit;
|
||||||
|
|
||||||
|
|
||||||
{ TTestTSQLQuery }
|
{ TTestTSQLQuery }
|
||||||
|
|
||||||
|
procedure TTestTSQLQuery.DoAfterPost(DataSet: TDataSet);
|
||||||
|
begin
|
||||||
|
AssertTrue('Have modifications in after post',FMyq.UpdateStatus=usModified)
|
||||||
|
end;
|
||||||
|
|
||||||
|
Procedure TTestTSQLQuery.Allow;
|
||||||
|
begin
|
||||||
|
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TTestTSQLQuery.TestMasterDetail;
|
procedure TTestTSQLQuery.TestMasterDetail;
|
||||||
var MasterQuery, DetailQuery: TSQLQuery;
|
var MasterQuery, DetailQuery: TSQLQuery;
|
||||||
MasterSource: TDataSource;
|
MasterSource: TDataSource;
|
||||||
@ -141,6 +161,168 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
Procedure TTestTSQLQuery.TestDisconnected;
|
||||||
|
var Q: TSQLQuery;
|
||||||
|
I, J : Integer;
|
||||||
|
begin
|
||||||
|
// Test that for a disconnected SQL query, calling commit does not close the dataset.
|
||||||
|
// Test also that an edit still works.
|
||||||
|
with TSQLDBConnector(DBConnector) do
|
||||||
|
begin
|
||||||
|
try
|
||||||
|
ExecuteDirect('DROP table testdiscon');
|
||||||
|
except
|
||||||
|
// Ignore
|
||||||
|
end;
|
||||||
|
ExecuteDirect('create table testdiscon (id integer not null, a varchar(10), constraint pk_testdiscon primary key(id))');
|
||||||
|
Transaction.COmmit;
|
||||||
|
for I:=1 to 20 do
|
||||||
|
ExecuteDirect(Format('INSERT INTO testdiscon values (%d,''%.6d'')',[i,i]));
|
||||||
|
Transaction.COmmit;
|
||||||
|
Q := TSQLDBConnector(DBConnector).Query;
|
||||||
|
Q.SQL.Text:='select * from testdiscon';
|
||||||
|
Q.QueryOptions:=[sqoDisconnected];
|
||||||
|
AssertEquals('PacketRecords forced to -1',-1,Q.PacketRecords);
|
||||||
|
Q.Open;
|
||||||
|
AssertEquals('Got all records',20,Q.RecordCount);
|
||||||
|
Q.SQLTransaction.Commit;
|
||||||
|
AssertTrue('Still open after transaction',Q.Active);
|
||||||
|
// Now check editing
|
||||||
|
Q.Locate('id',20,[]);
|
||||||
|
Q.Edit;
|
||||||
|
Q.FieldByName('a').AsString:='abc';
|
||||||
|
Q.Post;
|
||||||
|
AssertTrue('Have updates pending',Q.UpdateStatus=usModified);
|
||||||
|
Q.ApplyUpdates;
|
||||||
|
AssertTrue('Have no more updates pending',Q.UpdateStatus=usUnmodified);
|
||||||
|
Q.Close;
|
||||||
|
Q.SQL.Text:='select * from testdiscon where (id=20) and (a=''abc'')';
|
||||||
|
Q.Open;
|
||||||
|
AssertTrue('Have modified data record in database',not (Q.EOF AND Q.BOF));
|
||||||
|
end;
|
||||||
|
|
||||||
|
end;
|
||||||
|
|
||||||
|
Procedure TTestTSQLQuery.TrySetPacketRecords;
|
||||||
|
|
||||||
|
begin
|
||||||
|
FMyQ.PacketRecords:=10;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Procedure TTestTSQLQuery.TestDisconnectedPacketRecords;
|
||||||
|
begin
|
||||||
|
with TSQLDBConnector(DBConnector) do
|
||||||
|
begin
|
||||||
|
FMyQ := TSQLDBConnector(DBConnector).Query;
|
||||||
|
FMyQ.QueryOptions:=[sqoDisconnected];
|
||||||
|
AssertException('Cannot set packetrecords when sqoDisconnected is active',EDatabaseError,@TrySetPacketRecords);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Procedure TTestTSQLQuery.SetQueryOPtions;
|
||||||
|
|
||||||
|
begin
|
||||||
|
FMyQ.QueryOptions:=[sqoDisconnected];
|
||||||
|
end;
|
||||||
|
|
||||||
|
Procedure TTestTSQLQuery.TestCheckSettingsOnlyWhenInactive;
|
||||||
|
begin
|
||||||
|
// Check that we can only set QueryOptions when the query is inactive.
|
||||||
|
with TSQLDBConnector(DBConnector) do
|
||||||
|
begin
|
||||||
|
try
|
||||||
|
ExecuteDirect('DROP table testdiscon');
|
||||||
|
except
|
||||||
|
// Ignore
|
||||||
|
end;
|
||||||
|
ExecuteDirect('create table testdiscon (id integer not null, a varchar(10), constraint pk_testdiscon primary key(id))');
|
||||||
|
Transaction.COmmit;
|
||||||
|
ExecuteDirect(Format('INSERT INTO testdiscon values (%d,''%.6d'')',[1,1]));
|
||||||
|
Transaction.COmmit;
|
||||||
|
FMyQ := TSQLDBConnector(DBConnector).Query;
|
||||||
|
FMyQ.SQL.Text:='select * from testdiscon';
|
||||||
|
FMyQ := TSQLDBConnector(DBConnector).Query;
|
||||||
|
FMyQ.OPen;
|
||||||
|
AssertException('Cannot set packetrecords when sqoDisconnected is active',EDatabaseError,@SetQueryOptions);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Procedure TTestTSQLQuery.TestAutoApplyUpdatesPost;
|
||||||
|
var Q: TSQLQuery;
|
||||||
|
I, J : Integer;
|
||||||
|
begin
|
||||||
|
// Test that if sqoAutoApplyUpdates is in QueryOptions, then POST automatically does an ApplyUpdates
|
||||||
|
// Test also that POST afterpost event is backwards compatible.
|
||||||
|
with TSQLDBConnector(DBConnector) do
|
||||||
|
begin
|
||||||
|
try
|
||||||
|
ExecuteDirect('DROP table testdiscon');
|
||||||
|
except
|
||||||
|
// Ignore
|
||||||
|
end;
|
||||||
|
ExecuteDirect('create table testdiscon (id integer not null, a varchar(10), constraint pk_testdiscon primary key(id))');
|
||||||
|
Transaction.COmmit;
|
||||||
|
for I:=1 to 2 do
|
||||||
|
ExecuteDirect(Format('INSERT INTO testdiscon values (%d,''%.6d'')',[i,i]));
|
||||||
|
Transaction.COmmit;
|
||||||
|
Q := TSQLDBConnector(DBConnector).Query;
|
||||||
|
FMyQ:=Q; // so th event handler can reach it.
|
||||||
|
Q.SQL.Text:='select * from testdiscon';
|
||||||
|
Q.QueryOptions:=[ sqoAutoApplyUpdates];
|
||||||
|
// We must test that in AfterPost, the modification is still there, for backwards compatibilty
|
||||||
|
Q.AfterPost:=@DoAfterPost;
|
||||||
|
Q.Open;
|
||||||
|
AssertEquals('Got all records',2,Q.RecordCount);
|
||||||
|
// Now check editing
|
||||||
|
Q.Locate('id',2,[]);
|
||||||
|
Q.Edit;
|
||||||
|
Q.FieldByName('a').AsString:='abc';
|
||||||
|
Q.Post;
|
||||||
|
AssertTrue('Have no more updates pending',Q.UpdateStatus=usUnmodified);
|
||||||
|
Q.Close;
|
||||||
|
Q.SQL.Text:='select * from testdiscon where (id=2) and (a=''abc'')';
|
||||||
|
Q.Open;
|
||||||
|
AssertTrue('Have modified data record in database',not (Q.EOF AND Q.BOF));
|
||||||
|
end;
|
||||||
|
|
||||||
|
end;
|
||||||
|
|
||||||
|
Procedure TTestTSQLQuery.TestAutoApplyUpdatesDelete;
|
||||||
|
var Q: TSQLQuery;
|
||||||
|
I, J : Integer;
|
||||||
|
begin
|
||||||
|
// Test that if sqoAutoApplyUpdates is in QueryOptions, then Delete automatically does an ApplyUpdates
|
||||||
|
with TSQLDBConnector(DBConnector) do
|
||||||
|
begin
|
||||||
|
try
|
||||||
|
ExecuteDirect('DROP table testdiscon');
|
||||||
|
except
|
||||||
|
// Ignore
|
||||||
|
end;
|
||||||
|
ExecuteDirect('create table testdiscon (id integer not null, a varchar(10), constraint pk_testdiscon primary key(id))');
|
||||||
|
Transaction.COmmit;
|
||||||
|
for I:=1 to 2 do
|
||||||
|
ExecuteDirect(Format('INSERT INTO testdiscon values (%d,''%.6d'')',[i,i]));
|
||||||
|
Transaction.COmmit;
|
||||||
|
Q := TSQLDBConnector(DBConnector).Query;
|
||||||
|
FMyQ:=Q; // so th event handler can reach it.
|
||||||
|
Q.SQL.Text:='select * from testdiscon';
|
||||||
|
Q.QueryOptions:=[ sqoAutoApplyUpdates];
|
||||||
|
// We must test that in AfterPost, the modification is still there, for backwards compatibilty
|
||||||
|
Q.AfterPost:=@DoAfterPost;
|
||||||
|
Q.Open;
|
||||||
|
AssertEquals('Got all records',2,Q.RecordCount);
|
||||||
|
// Now check editing
|
||||||
|
Q.Locate('id',2,[]);
|
||||||
|
Q.Delete;
|
||||||
|
AssertTrue('Have no more updates pending',Q.UpdateStatus=usUnmodified);
|
||||||
|
Q.Close;
|
||||||
|
Q.SQL.Text:='select * from testdiscon where (id=2)';
|
||||||
|
Q.Open;
|
||||||
|
AssertTrue('Data record is deleted in database', (Q.EOF AND Q.BOF));
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
{ TTestTSQLConnection }
|
{ TTestTSQLConnection }
|
||||||
|
|
||||||
procedure TTestTSQLConnection.ReplaceMe;
|
procedure TTestTSQLConnection.ReplaceMe;
|
||||||
|
Loading…
Reference in New Issue
Block a user