Skip to content

Commit

Permalink
another boInsertOrIgnore/boInsertOrReplace BatchOptions fix
Browse files Browse the repository at this point in the history
  • Loading branch information
Arnaud Bouchez committed Jun 20, 2022
1 parent 1ab7f9b commit 677e26a
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 48 deletions.
66 changes: 48 additions & 18 deletions src/db/mormot.db.core.pas
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);



Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
26 changes: 3 additions & 23 deletions src/db/mormot.db.sql.pas
Expand Up @@ -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 = (
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/mormot.commit.inc
@@ -1 +1 @@
'2.0.3483'
'2.0.3484'
2 changes: 1 addition & 1 deletion src/orm/mormot.orm.base.pas
Expand Up @@ -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')
Expand Down
2 changes: 1 addition & 1 deletion src/orm/mormot.orm.core.pas
Expand Up @@ -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')
Expand Down
9 changes: 5 additions & 4 deletions src/orm/mormot.orm.sqlite3.pas
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit 677e26a

Please sign in to comment.