|
@@ -138,7 +138,7 @@ type
|
|
|
|
|
|
{ TSQLConnection }
|
|
|
|
|
|
- TConnOption = (sqSupportParams, sqSupportEmptyDatabaseName, sqEscapeSlash, sqEscapeRepeat, sqImplicitTransaction, sqLastInsertID);
|
|
|
+ TConnOption = (sqSupportParams, sqSupportEmptyDatabaseName, sqEscapeSlash, sqEscapeRepeat, sqImplicitTransaction, sqLastInsertID, sqSupportReturning);
|
|
|
TConnOptions= set of TConnOption;
|
|
|
|
|
|
TSQLConnectionOption = (scoExplicitConnect, scoApplyUpdatesChecksRowsAffected);
|
|
@@ -172,11 +172,11 @@ type
|
|
|
// One day, this may be factored out to a TSQLResolver class.
|
|
|
// The following allow construction of update queries. They can be adapted as needed by descendents to fit the DB engine.
|
|
|
procedure AddFieldToUpdateWherePart(var sql_where: string; UpdateMode : TUpdateMode; F: TField); virtual;
|
|
|
- function ConstructInsertSQL(Query: TCustomSQLQuery): string; virtual;
|
|
|
- function ConstructUpdateSQL(Query: TCustomSQLQuery): string; virtual;
|
|
|
+ function ConstructInsertSQL(Query: TCustomSQLQuery; Var ReturningClause : Boolean): string; virtual;
|
|
|
+ function ConstructUpdateSQL(Query: TCustomSQLQuery; Var ReturningClause : Boolean): string; virtual;
|
|
|
function ConstructDeleteSQL(Query: TCustomSQLQuery): string; virtual;
|
|
|
function ConstructRefreshSQL(Query: TCustomSQLQuery; UpdateKind : TUpdateKind): string; virtual;
|
|
|
- function InitialiseUpdateStatement(Query: TCustomSQLQuery; var qry: TCustomSQLStatement): TCustomSQLStatement;
|
|
|
+ function InitialiseUpdateStatement(Query: TCustomSQLQuery; var qry: TCustomSQLQuery): TCustomSQLQuery;
|
|
|
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.
|
|
|
procedure ApplyRecUpdate(Query : TCustomSQLQuery; UpdateKind : TUpdateKind); virtual;
|
|
@@ -402,7 +402,7 @@ type
|
|
|
|
|
|
{ TCustomSQLQuery }
|
|
|
|
|
|
- TSQLQueryOption = (sqoKeepOpenOnCommit, sqoAutoApplyUpdates, sqoAutoCommit, sqoCancelUpdatesOnRefresh);
|
|
|
+ TSQLQueryOption = (sqoKeepOpenOnCommit, sqoAutoApplyUpdates, sqoAutoCommit, sqoCancelUpdatesOnRefresh, sqoPreferRefresh);
|
|
|
TSQLQueryOptions = Set of TSQLQueryOption;
|
|
|
|
|
|
TCustomSQLQuery = class (TCustomBufDataset)
|
|
@@ -433,7 +433,7 @@ type
|
|
|
|
|
|
FInsertQry,
|
|
|
FUpdateQry,
|
|
|
- FDeleteQry : TCustomSQLStatement;
|
|
|
+ FDeleteQry : TCustomSQLQuery;
|
|
|
FSequence : TSQLSequence;
|
|
|
procedure FreeFldBuffers;
|
|
|
function GetParamCheck: Boolean;
|
|
@@ -466,6 +466,7 @@ type
|
|
|
Function RefreshLastInsertID(Field: TField): Boolean; virtual;
|
|
|
Function NeedRefreshRecord (UpdateKind: TUpdateKind): Boolean; virtual;
|
|
|
Function RefreshRecord (UpdateKind: TUpdateKind) : Boolean; virtual;
|
|
|
+ Procedure ApplyReturningResult(Q : TCustomSQLQuery; UpdateKind : TUpdateKind);
|
|
|
Function Cursor : TSQLCursor;
|
|
|
Function LogEvent(EventType : TDBEventType) : Boolean;
|
|
|
Procedure Log(EventType : TDBEventType; Const Msg : String); virtual;
|
|
@@ -1587,15 +1588,18 @@ begin
|
|
|
end;
|
|
|
|
|
|
|
|
|
-function TSQLConnection.InitialiseUpdateStatement(Query : TCustomSQLQuery; var qry : TCustomSQLStatement): TCustomSQLStatement;
|
|
|
+function TSQLConnection.InitialiseUpdateStatement(Query : TCustomSQLQuery; var qry : TCustomSQLQuery): TCustomSQLQuery;
|
|
|
|
|
|
begin
|
|
|
if not assigned(qry) then
|
|
|
begin
|
|
|
- qry := TCustomSQLStatement.Create(nil);
|
|
|
+ qry := TCustomSQLQuery.Create(nil);
|
|
|
qry.ParseSQL := False;
|
|
|
qry.DataBase := Self;
|
|
|
qry.Transaction := Query.SQLTransaction;
|
|
|
+ qry.Unidirectional:=True;
|
|
|
+ qry.UsePrimaryKeyAsKey:=False;
|
|
|
+ qry.PacketRecords:=1;
|
|
|
end;
|
|
|
Result:=qry;
|
|
|
end;
|
|
@@ -1620,16 +1624,19 @@ begin
|
|
|
end;
|
|
|
|
|
|
|
|
|
-function TSQLConnection.ConstructInsertSQL(Query : TCustomSQLQuery) : string;
|
|
|
+function TSQLConnection.ConstructInsertSQL(Query : TCustomSQLQuery; Var ReturningClause : Boolean) : string;
|
|
|
|
|
|
var x : integer;
|
|
|
sql_fields : string;
|
|
|
sql_values : string;
|
|
|
+ returning_fields : String;
|
|
|
F : TField;
|
|
|
|
|
|
+
|
|
|
begin
|
|
|
sql_fields := '';
|
|
|
sql_values := '';
|
|
|
+ returning_fields :='';
|
|
|
for x := 0 to Query.Fields.Count -1 do
|
|
|
begin
|
|
|
F:=Query.Fields[x];
|
|
@@ -1638,37 +1645,60 @@ begin
|
|
|
sql_fields := sql_fields + FieldNameQuoteChars[0] + F.FieldName + FieldNameQuoteChars[1] + ',';
|
|
|
sql_values := sql_values + ':"' + F.FieldName + '",';
|
|
|
end;
|
|
|
+ if ReturningClause and (pfRefreshOnInsert in F.ProviderFlags) then
|
|
|
+ returning_fields :=returning_fields+FieldNameQuoteChars[0] + F.FieldName + FieldNameQuoteChars[1] + ',';
|
|
|
end;
|
|
|
if length(sql_fields) = 0 then
|
|
|
DatabaseErrorFmt(sNoUpdateFields,['insert'],self);
|
|
|
setlength(sql_fields,length(sql_fields)-1);
|
|
|
setlength(sql_values,length(sql_values)-1);
|
|
|
-
|
|
|
result := 'insert into ' + Query.FTableName + ' (' + sql_fields + ') values (' + sql_values + ')';
|
|
|
+ if ReturningClause then
|
|
|
+ begin
|
|
|
+ ReturningClause:=length(returning_fields) <> 0 ;
|
|
|
+ if ReturningClause then
|
|
|
+ begin
|
|
|
+ setlength(returning_fields,length(returning_fields)-1);
|
|
|
+ result:=Result+' returning '+returning_fields;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
end;
|
|
|
|
|
|
|
|
|
-function TSQLConnection.ConstructUpdateSQL(Query: TCustomSQLQuery): string;
|
|
|
+function TSQLConnection.ConstructUpdateSQL(Query: TCustomSQLQuery; Var ReturningClause : Boolean): string;
|
|
|
|
|
|
var x : integer;
|
|
|
F : TField;
|
|
|
sql_set : string;
|
|
|
sql_where : string;
|
|
|
+ returning_fields : String;
|
|
|
|
|
|
begin
|
|
|
sql_set := '';
|
|
|
sql_where := '';
|
|
|
+ returning_fields :='';
|
|
|
for x := 0 to Query.Fields.Count -1 do
|
|
|
begin
|
|
|
F:=Query.Fields[x];
|
|
|
AddFieldToUpdateWherePart(sql_where,Query.UpdateMode,F);
|
|
|
if (pfInUpdate in F.ProviderFlags) and (not F.ReadOnly) then
|
|
|
sql_set := sql_set +FieldNameQuoteChars[0] + F.FieldName + FieldNameQuoteChars[1] +'=:"' + F.FieldName + '",';
|
|
|
+ if ReturningClause and (pfRefreshOnUpdate in F.ProviderFlags) then
|
|
|
+ returning_fields :=returning_fields+FieldNameQuoteChars[0] + F.FieldName + FieldNameQuoteChars[1] + ',';
|
|
|
end;
|
|
|
if length(sql_set) = 0 then DatabaseErrorFmt(sNoUpdateFields,['update'],self);
|
|
|
setlength(sql_set,length(sql_set)-1);
|
|
|
if length(sql_where) = 0 then DatabaseErrorFmt(sNoWhereFields,['update'],self);
|
|
|
result := 'update ' + Query.FTableName + ' set ' + sql_set + ' where ' + sql_where;
|
|
|
+ if ReturningClause then
|
|
|
+ begin
|
|
|
+ ReturningClause:=length(returning_fields) <> 0 ;
|
|
|
+ if ReturningClause then
|
|
|
+ begin
|
|
|
+ setlength(returning_fields,length(returning_fields)-1);
|
|
|
+ result:=Result+' returning '+returning_fields;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
end;
|
|
|
|
|
|
|
|
@@ -1737,24 +1767,27 @@ end;
|
|
|
procedure TSQLConnection.ApplyRecUpdate(Query: TCustomSQLQuery; UpdateKind: TUpdateKind);
|
|
|
|
|
|
var
|
|
|
- qry : TCustomSQLStatement;
|
|
|
+ qry : TCustomSQLQuery;
|
|
|
s : string;
|
|
|
x : integer;
|
|
|
Fld : TField;
|
|
|
P : TParam;
|
|
|
- B : Boolean;
|
|
|
+ B,ReturningClause : Boolean;
|
|
|
|
|
|
begin
|
|
|
+ qry:=Nil;
|
|
|
+ ReturningClause:=(sqSupportReturning in Connoptions) and not (sqoPreferRefresh in Query.Options);
|
|
|
case UpdateKind of
|
|
|
ukInsert : begin
|
|
|
s := trim(Query.FInsertSQL.Text);
|
|
|
- if s = '' then s := ConstructInsertSQL(Query);
|
|
|
+ if s = '' then
|
|
|
+ s := ConstructInsertSQL(Query,ReturningClause);
|
|
|
qry := InitialiseUpdateStatement(Query,Query.FInsertQry);
|
|
|
end;
|
|
|
ukModify : begin
|
|
|
s := trim(Query.FUpdateSQL.Text);
|
|
|
if (s='') and (not assigned(Query.FUpdateQry) or (Query.UpdateMode<>upWhereKeyOnly)) then //first time or dynamic where part
|
|
|
- s := ConstructUpdateSQL(Query);
|
|
|
+ s := ConstructUpdateSQL(Query,ReturningClause);
|
|
|
qry := InitialiseUpdateStatement(Query,Query.FUpdateQry);
|
|
|
end;
|
|
|
ukDelete : begin
|
|
@@ -1762,11 +1795,12 @@ begin
|
|
|
if (s='') and (not assigned(Query.FDeleteQry) or (Query.UpdateMode<>upWhereKeyOnly)) then
|
|
|
s := ConstructDeleteSQL(Query);
|
|
|
qry := InitialiseUpdateStatement(Query,Query.FDeleteQry);
|
|
|
+ ReturningClause:=False;
|
|
|
end;
|
|
|
end;
|
|
|
if (s<>'') and (qry.SQL.Text<>s) then
|
|
|
qry.SQL.Text:=s; //assign only when changed, to avoid UnPrepare/Prepare
|
|
|
- assert(qry.sql.Text<>'');
|
|
|
+ Assert(qry.sql.Text<>'');
|
|
|
for x:=0 to Qry.Params.Count-1 do
|
|
|
begin
|
|
|
P:=Qry.Params[x];
|
|
@@ -1777,9 +1811,18 @@ begin
|
|
|
Fld:=Query.FieldByName(S);
|
|
|
ApplyFieldUpdate(Query.Cursor,P as TSQLDBParam,Fld,B);
|
|
|
end;
|
|
|
- Qry.Execute;
|
|
|
+ if ReturningClause then
|
|
|
+ Qry.Open
|
|
|
+ else
|
|
|
+ Qry.Execute;
|
|
|
if (scoApplyUpdatesChecksRowsAffected in Options) and (Qry.RowsAffected<>1) then
|
|
|
+ begin
|
|
|
+ if ReturningClause then
|
|
|
+ Qry.Close;
|
|
|
DatabaseErrorFmt(SErrFailedToUpdateRecord, [Qry.RowsAffected], Query);
|
|
|
+ end;
|
|
|
+ if ReturningClause then
|
|
|
+ Query.ApplyReturningResult(Qry,UpdateKind);
|
|
|
end;
|
|
|
|
|
|
function TSQLConnection.RefreshLastInsertID(Query: TCustomSQLQuery; Field: TField): Boolean;
|
|
@@ -2310,9 +2353,12 @@ function TCustomSQLQuery.NeedRefreshRecord(UpdateKind: TUpdateKind): Boolean;
|
|
|
Var
|
|
|
PF : TProviderFlag;
|
|
|
I : Integer;
|
|
|
+ DoReturning : Boolean;
|
|
|
+
|
|
|
begin
|
|
|
Result:=(FRefreshSQL.Count<>0);
|
|
|
- if Not Result then
|
|
|
+ DoReturning:=(sqSupportReturning in SQLConnection.ConnOptions) and not (sqoPreferRefresh in Options);
|
|
|
+ if Not (Result or DoReturning) then
|
|
|
begin
|
|
|
PF:=RefreshFlags[UpdateKind];
|
|
|
I:=0;
|
|
@@ -2374,6 +2420,25 @@ begin
|
|
|
end;
|
|
|
end;
|
|
|
|
|
|
+procedure TCustomSQLQuery.ApplyReturningResult(Q: TCustomSQLQuery; UpdateKind : TUpdateKind);
|
|
|
+
|
|
|
+Var
|
|
|
+ S : TDataSetState;
|
|
|
+ refreshFlag : TProviderFlag;
|
|
|
+ F : TField;
|
|
|
+
|
|
|
+begin
|
|
|
+ RefreshFlag:=RefreshFlags[UpdateKind];
|
|
|
+ S:=SetTempState(dsRefreshFields);
|
|
|
+ try
|
|
|
+ For F in Fields do
|
|
|
+ if RefreshFlag in F.ProviderFlags then
|
|
|
+ F.Assign(Q.FieldByName(F.FieldName));
|
|
|
+ finally
|
|
|
+ RestoreState(S);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
procedure TCustomSQLQuery.ApplyFilter;
|
|
|
|
|
|
begin
|