From 677e26a63aa467ea0677d7a029d12453d9d3de4f Mon Sep 17 00:00:00 2001 From: Arnaud Bouchez Date: Mon, 20 Jun 2022 16:59:20 +0200 Subject: [PATCH] another boInsertOrIgnore/boInsertOrReplace BatchOptions fix --- src/db/mormot.db.core.pas | 66 ++++++++++++++++++++++++---------- src/db/mormot.db.sql.pas | 26 ++------------ src/mormot.commit.inc | 2 +- src/orm/mormot.orm.base.pas | 2 +- src/orm/mormot.orm.core.pas | 2 +- src/orm/mormot.orm.sqlite3.pas | 9 ++--- 6 files changed, 59 insertions(+), 48 deletions(-) diff --git a/src/db/mormot.db.core.pas b/src/db/mormot.db.core.pas index 7aaae9e51..9e83831cb 100644 --- a/src/db/mormot.db.core.pas +++ b/src/db/mormot.db.core.pas @@ -1095,11 +1095,31 @@ procedure SetID(const U: RawByteString; var result: TID); overload; { ************ JSON Object Decoder and SQL Generation } type + /// the supported SQL database dialects + // - will be used e.g. for TSqlDBConnectionProperties.SqlFieldCreate(), or + // for OleDB/ODBC/ZDBC tuning according to the connected database engine + TSqlDBDefinition = ( + dUnknown, + dDefault, + dOracle, + dMSSQL, + dJet, + dMySQL, + dSQLite, + dFirebird, + dNexusDB, + dPostgreSQL, + dDB2, + dInformix); + + /// set of the available database definitions + TSqlDBDefinitions = set of TSqlDBDefinition; + /// the available options for TRest.BatchStart() process // - boInsertOrIgnore will create 'INSERT OR IGNORE' statements instead of - // plain 'INSERT' - by now, only the direct SQLite3 engine supports it - // - boInsertOrUpdate will create 'INSERT OR REPLACE' statements instead of - // plain 'INSERT' - by now, only the direct SQLite3 engine supports it + // plain 'INSERT' - supported only by SQLite3 + // - boInsertOrUpdate will create 'REPLACE' statements instead of + // plain 'INSERT' - supported only by SQLite3, Firebird and MySQL // - boExtendedJson will force the JSON to unquote the column names, // e.g. writing col1:...,col2:... instead of "col1":...,"col2"... // - boPostNoSimpleFields (client-side only) will avoid to send a @@ -1224,7 +1244,7 @@ TJsonObjectDecoder = record // 'COL1=:("VAL1"):, COL2=:(VAL2):' // - called by GetJsonObjectAsSql() function or TRestStorageExternal function EncodeAsSql(const Prefix1, Prefix2: RawUtf8; Update: boolean; - Prefix1Batch: PRestBatchOptions; Firebird: boolean): RawUtf8; + Prefix1Batch: PRestBatchOptions; DB: TSqlDBDefinition): RawUtf8; /// encode the FieldNames/FieldValues[] as a JSON object procedure EncodeAsJson(out result: RawUtf8); /// set the specified array to the fields names @@ -1295,11 +1315,14 @@ function JsonGetObject(var P: PUtf8Char; ExtractID: PID; // - returns TRUE if a ID/RowID>0 has been found, and set ID with the value function JsonGetID(P: PUtf8Char; out ID: TID): boolean; -/// append "insert [or replace/or ignore] into ' text into W +/// append "insert / insert or ignore / replace into ' text into W // - depending on boInsertOrIgnore/boInsertOrReplace presence in BatchOptions -// - Firebird has its "UPDATE OR INSERT INTO" strange syntax -procedure EncodeInsert(W: TTextWriter; BatchOptions: TRestBatchOptions; - Firebird: boolean); +// - SQLite3 and MySQL should understand "REPLACE INTO" +// - Firebird has its "UPDATE OR INSERT INTO" own syntax +// - other databases are not supported, because they require a much more complex +// SQL statement to produce the same effect - a prefix is not enough +procedure EncodeInsertPrefix(W: TTextWriter; BatchOptions: TRestBatchOptions; + DB: TSqlDBDefinition); @@ -3526,7 +3549,7 @@ procedure TJsonObjectDecoder.AssignFieldNamesTo(var Fields: TRawUtf8DynArray); end; function TJsonObjectDecoder.EncodeAsSql(const Prefix1, Prefix2: RawUtf8; - Update: boolean; Prefix1Batch: PRestBatchOptions; Firebird: boolean): RawUtf8; + Update: boolean; Prefix1Batch: PRestBatchOptions; DB: TSqlDBDefinition): RawUtf8; var f: PtrInt; W: TJsonWriter; @@ -3550,7 +3573,7 @@ function TJsonObjectDecoder.EncodeAsSql(const Prefix1, Prefix2: RawUtf8; W := TJsonWriter.CreateOwnedStream(temp); try if Prefix1Batch <> nil then - EncodeInsert(W, Prefix1Batch^, Firebird) + EncodeInsertPrefix(W, Prefix1Batch^, DB) else W.AddString(Prefix1); W.AddString(Prefix2); @@ -3658,7 +3681,7 @@ function GetJsonObjectAsSql(var P: PUtf8Char; const Fields: TRawUtf8DynArray; Decoder: TJsonObjectDecoder; begin Decoder.Decode(P, Fields, FROMINLINED[InlinedParams], RowID, ReplaceRowIDWithID); - result := Decoder.EncodeAsSql('', '', Update, nil, false); + result := Decoder.EncodeAsSql('', '', Update, nil, dUnknown); end; function GetJsonObjectAsSql(const Json: RawUtf8; Update, InlinedParams: boolean; @@ -3667,7 +3690,7 @@ function GetJsonObjectAsSql(const Json: RawUtf8; Update, InlinedParams: boolean; Decoder: TJsonObjectDecoder; begin Decoder.Decode(Json, nil, FROMINLINED[InlinedParams], RowID, ReplaceRowIDWithID); - result := Decoder.EncodeAsSql('', '', Update, nil, false); + result := Decoder.EncodeAsSql('', '', Update, nil, dUnknown); end; function UnJsonFirstField(var P: PUtf8Char): RawUtf8; @@ -3810,16 +3833,23 @@ function JsonGetObject(var P: PUtf8Char; ExtractID: PID; end; end; -procedure EncodeInsert(W: TTextWriter; BatchOptions: TRestBatchOptions; - Firebird: boolean); +procedure EncodeInsertPrefix(W: TTextWriter; BatchOptions: TRestBatchOptions; + DB: TSqlDBDefinition); begin if boInsertOrIgnore in BatchOptions then - W.AddShort('insert or ignore into ') + case DB of + dMySQL: + W.AddShort('insert ignore into ') + else + W.AddShort('insert or ignore into '); // SQlite3 + end else if boInsertOrReplace in BatchOptions then - if Firebird then - W.AddShort('update or insert into ') // weird syntax + case DB of + dFirebird: + W.AddShort('update or insert into '); else - W.AddShort('insert or replace into ') + W.AddShort('replace into '); // SQlite3 and MySQL + end else W.AddShort('insert into '); end; diff --git a/src/db/mormot.db.sql.pas b/src/db/mormot.db.sql.pas index 5abddd0ce..4570fa74c 100644 --- a/src/db/mormot.db.sql.pas +++ b/src/db/mormot.db.sql.pas @@ -293,26 +293,6 @@ TSqlDBColumnCreate = record { ************ Define Database Engine Specific Behavior } type - /// the supported SQL database dialects - // - will be used e.g. for TSqlDBConnectionProperties.SqlFieldCreate(), or - // for OleDB/ODBC/ZDBC tuning according to the connected database engine - TSqlDBDefinition = ( - dUnknown, - dDefault, - dOracle, - dMSSQL, - dJet, - dMySQL, - dSQLite, - dFirebird, - dNexusDB, - dPostgreSQL, - dDB2, - dInformix); - - /// set of the available database definitions - TSqlDBDefinitions = set of TSqlDBDefinition; - /// where the LIMIT clause should be inserted for a given SQL syntax // - used by TSqlDBDefinitionLimitClause and SqlLimitClause() method TSqlDBDefinitionLimitPosition = ( @@ -5073,7 +5053,7 @@ procedure TSqlDBConnectionProperties.MultipleValuesInsert( p := 0; for r := 1 to rowcount do begin - EncodeInsert(W, BatchOptions, {Firebird=}true); + EncodeInsertPrefix(W, BatchOptions, dFirebird); W.AddString(TableName); W.Add(' ', '('); for f := 0 to maxf do @@ -5128,7 +5108,7 @@ procedure TSqlDBConnectionProperties.MultipleValuesInsert( begin // e.g. NexusDB/SQlite3/MySQL/PostgreSQL/MSSQL2008/DB2/INFORMIX // INSERT .. VALUES (..),(..),(..),.. - EncodeInsert(W, BatchOptions, false); // Firebird is done above + EncodeInsertPrefix(W, BatchOptions, Props.fDbms); W.AddString(TableName); W.Add(' ', '('); for f := 0 to maxf do @@ -5285,7 +5265,7 @@ procedure TSqlDBConnectionProperties.MultipleValuesInsertFirebird( inc(sqllen, length(FieldValues[f, r])); if sqllen + PtrInt(W.TextLength) > 30000 then break; - EncodeInsert(W, BatchOptions, {Firebird=}true); + EncodeInsertPrefix(W, BatchOptions, dFirebird); W.AddString(TableName); W.Add(' ', '('); for f := 0 to maxf do diff --git a/src/mormot.commit.inc b/src/mormot.commit.inc index 7c6a92a2e..3a678d322 100644 --- a/src/mormot.commit.inc +++ b/src/mormot.commit.inc @@ -1 +1 @@ -'2.0.3483' +'2.0.3484' diff --git a/src/orm/mormot.orm.base.pas b/src/orm/mormot.orm.base.pas index 049eaeeb6..87ed22e77 100644 --- a/src/orm/mormot.orm.base.pas +++ b/src/orm/mormot.orm.base.pas @@ -3427,7 +3427,7 @@ function EncodeAsSqlPrepared(const Decoder: TJsonObjectDecoder; end; ooInsert: begin - EncodeInsert(W, BatchOptions, {firebird=}false); + EncodeInsertPrefix(W, BatchOptions, dSQLite); W.AddString(TableName); if Decoder.FieldCount = 0 then W.AddShort(' default values') diff --git a/src/orm/mormot.orm.core.pas b/src/orm/mormot.orm.core.pas index 8bbdb944d..da521fc29 100644 --- a/src/orm/mormot.orm.core.pas +++ b/src/orm/mormot.orm.core.pas @@ -4997,7 +4997,7 @@ procedure EncodeMultiInsertSQLite3(Props: TOrmProperties; begin W := TJsonWriter.CreateOwnedStream(temp); try - EncodeInsert(W, BatchOptions, {Firebird=}false); + EncodeInsertPrefix(W, BatchOptions, dSQLite); W.AddString(Props.SqlTableName); if FieldCount = 0 then W.AddShort(' default values') diff --git a/src/orm/mormot.orm.sqlite3.pas b/src/orm/mormot.orm.sqlite3.pas index ea5e14b86..858acca5c 100644 --- a/src/orm/mormot.orm.sqlite3.pas +++ b/src/orm/mormot.orm.sqlite3.pas @@ -1451,7 +1451,8 @@ function TRestOrmServerDB.MainEngineAdd(TableModelIndex: integer; (props.RecordVersionField <> nil) then fOwner.RecordVersionHandle(ooInsert, TableModelIndex, fJsonDecoder, props.RecordVersionField); - sql := fJsonDecoder.EncodeAsSql('INSERT INTO ', sql, {update=}false, nil, false); + sql := fJsonDecoder.EncodeAsSql( + 'INSERT INTO ', sql, {update=}false, nil, dSQLite); Finalize(fJsonDecoder); // release temp values memory ASAP finally fRest.AcquireExecution[execOrmWrite].Safe.UnLock; @@ -2058,7 +2059,7 @@ function TRestOrmServerDB.MainEngineUpdate(TableModelIndex: integer; ID: TID; (fOwner <> nil) then fOwner.RecordVersionHandle(ooUpdate, TableModelIndex, fJsonDecoder, props.RecordVersionField); - sql := fJsonDecoder.EncodeAsSql('', '', {update=}true, nil, false); + sql := fJsonDecoder.EncodeAsSql('', '', {update=}true, nil, dSQLite); Finalize(fJsonDecoder); // release temp values memory ASAP finally fRest.AcquireExecution[execOrmWrite].Safe.UnLock; @@ -2458,8 +2459,8 @@ procedure TRestOrmServerDB.InternalBatchStop; (fOwner <> nil) then fOwner.RecordVersionHandle( ooInsert, b^.TableIndex, fJsonDecoder, props.RecordVersionField); - sql := fJsonDecoder.EncodeAsSql('', props.SqlTableName, {update=}false, - @b^.Options, {Firebird=}false); + sql := fJsonDecoder.EncodeAsSql( + '', props.SqlTableName, {update=}false, @b^.Options, dSQLite); if not InternalExecute(sql, {cache=}true) then // just like ESqlite3Exception below raise EOrmBatchException.CreateUtf8(