@@ -383,13 +383,10 @@ extension MutablePersistableRecord {
383383 mutating func upsertWithCallbacks( _ db: Database )
384384 throws -> InsertionSuccess
385385 {
386- let ( inserted , _ ) = try upsertAndFetchWithCallbacks (
386+ try upsertWithCallbacks (
387387 db, onConflict: [ ] ,
388388 updating: . allColumns,
389- doUpdate: nil ,
390- selection: [ ] ,
391- decode: { _ in /* Nothing to decode */ } )
392- return inserted
389+ doUpdate: nil )
393390 }
394391
395392 @inlinable // allow specialization so that empty callbacks are removed
@@ -421,6 +418,109 @@ extension MutablePersistableRecord {
421418 return success
422419 }
423420
421+ @inlinable // allow specialization so that empty callbacks are removed
422+ mutating func upsertWithCallbacks(
423+ _ db: Database ,
424+ onConflict conflictTarget: [ String ] ,
425+ updating strategy: UpsertUpdateStrategy ,
426+ doUpdate assignments: ( ( _ excluded: TableAlias < Self > ) -> [ ColumnAssignment ] ) ?
427+ ) throws -> InsertionSuccess {
428+ try willInsert ( db)
429+
430+ var inserted : InsertionSuccess ?
431+ try aroundInsert ( db) {
432+ inserted = try upsertWithoutCallbacks (
433+ db, onConflict: conflictTarget,
434+ updating: strategy, doUpdate: assignments)
435+ return inserted!
436+ }
437+
438+ guard let inserted else {
439+ try persistenceCallbackMisuse ( " aroundInsert " )
440+ }
441+ didInsert ( inserted)
442+ return inserted
443+ }
444+
445+ /// Executes an `INSERT` statement, and DOES NOT run
446+ /// insertion callbacks.
447+ @usableFromInline
448+ func upsertWithoutCallbacks(
449+ _ db: Database ,
450+ onConflict conflictTarget: [ String ] ,
451+ updating strategy: UpsertUpdateStrategy ,
452+ doUpdate assignments: ( ( _ excluded: TableAlias < Self > ) -> [ ColumnAssignment ] ) ? )
453+ throws -> InsertionSuccess
454+ {
455+ let primaryKey = try db. primaryKey ( Self . databaseTableName)
456+
457+ if primaryKey. tableHasRowID {
458+ let dao = try DAO ( db, self )
459+ let statement = try dao. upsertStatement (
460+ db, onConflict: conflictTarget,
461+ updating: strategy,
462+ doUpdate: assignments,
463+ updateCondition: nil ,
464+ returning: [ Column . rowID] )
465+ let cursor = try Row . fetchCursor ( statement)
466+
467+ // Keep cursor alive until we can process the fetched row
468+ let rowid : Int64 = try withExtendedLifetime ( cursor) { cursor in
469+ guard let row = try cursor. next ( ) else {
470+ throw DatabaseError ( message: " Insertion failed " )
471+ }
472+
473+ let rowid : Int64 = row [ 0 ]
474+
475+ // Now that we have fetched the values we need, we could stop
476+ // there. But let's make sure we fully consume the cursor
477+ // anyway, until SQLITE_DONE. This is necessary, for example,
478+ // for upserts in tables that are synchronized with an
479+ // FTS5 table.
480+ // See <https://github.com/groue/GRDB.swift/issues/1390>
481+ while try cursor. next ( ) != nil { }
482+
483+ return rowid
484+ }
485+
486+ // Update the persistenceContainer with the inserted rowid.
487+ // This allows the Record class to set its `hasDatabaseChanges` property
488+ // to false in its `aroundInsert` callback.
489+ var persistenceContainer = dao. persistenceContainer
490+ let rowIDColumn = dao. primaryKey. rowIDColumn
491+ if let rowIDColumn {
492+ persistenceContainer [ rowIDColumn] = rowid
493+ }
494+
495+ let inserted = InsertionSuccess (
496+ rowID: rowid,
497+ rowIDColumn: rowIDColumn,
498+ persistenceContainer: persistenceContainer)
499+ return inserted
500+ } else {
501+ let dao = try DAO ( db, self )
502+ let statement = try dao. upsertStatement (
503+ db, onConflict: conflictTarget,
504+ updating: strategy,
505+ doUpdate: assignments,
506+ updateCondition: nil ,
507+ returning: [ ] )
508+ let cursor = try Row . fetchCursor ( statement)
509+
510+ // Let's make sure we fully consume the cursor, until SQLITE_DONE.
511+ // This is necessary, for example, for upserts in tables that
512+ // are synchronized with an FTS5 table.
513+ // See <https://github.com/groue/GRDB.swift/issues/1390>
514+ while try cursor. next ( ) != nil { }
515+
516+ let inserted = InsertionSuccess (
517+ rowID: 0 , // Meaningless in WITHOUT ROWID tables.
518+ rowIDColumn: nil ,
519+ persistenceContainer: dao. persistenceContainer)
520+ return inserted
521+ }
522+ }
523+
424524 /// Executes an `INSERT RETURNING` statement, and DOES NOT run
425525 /// insertion callbacks.
426526 @usableFromInline
@@ -433,52 +533,90 @@ extension MutablePersistableRecord {
433533 decode: ( Row ) throws -> T )
434534 throws -> ( InsertionSuccess , T )
435535 {
436- // Append the rowID to the returned columns
437- let selection = selection + [ Column . rowID]
438-
439- let dao = try DAO ( db, self )
440- let statement = try dao. upsertStatement (
441- db, onConflict: conflictTarget,
442- updating: strategy,
443- doUpdate: assignments,
444- updateCondition: nil ,
445- returning: selection)
446- let cursor = try Row . fetchCursor ( statement)
536+ let primaryKey = try db. primaryKey ( Self . databaseTableName)
447537
448- // Keep cursor alive until we can process the fetched row
449- let ( rowid, returned) : ( Int64 , T ) = try withExtendedLifetime ( cursor) { cursor in
450- guard let row = try cursor. next ( ) else {
451- throw DatabaseError ( message: " Insertion failed " )
538+ if primaryKey. tableHasRowID {
539+ // Append the rowID to the returned columns
540+ let selection = selection + [ Column . rowID]
541+
542+ let dao = try DAO ( db, self )
543+ let statement = try dao. upsertStatement (
544+ db, onConflict: conflictTarget,
545+ updating: strategy,
546+ doUpdate: assignments,
547+ updateCondition: nil ,
548+ returning: selection)
549+ let cursor = try Row . fetchCursor ( statement)
550+
551+ // Keep cursor alive until we can process the fetched row
552+ let ( rowid, returned) : ( Int64 , T ) = try withExtendedLifetime ( cursor) { cursor in
553+ guard let row = try cursor. next ( ) else {
554+ throw DatabaseError ( message: " Insertion failed " )
555+ }
556+
557+ // Rowid is the last column
558+ let rowid : Int64 = row [ row. count - 1 ]
559+ let returned = try decode ( row)
560+
561+ // Now that we have fetched the values we need, we could stop
562+ // there. But let's make sure we fully consume the cursor
563+ // anyway, until SQLITE_DONE. This is necessary, for example,
564+ // for upserts in tables that are synchronized with an
565+ // FTS5 table.
566+ // See <https://github.com/groue/GRDB.swift/issues/1390>
567+ while try cursor. next ( ) != nil { }
568+
569+ return ( rowid, returned)
452570 }
453571
454- // Rowid is the last column
455- let rowid : Int64 = row [ row. count - 1 ]
456- let returned = try decode ( row)
572+ // Update the persistenceContainer with the inserted rowid.
573+ // This allows the Record class to set its `hasDatabaseChanges` property
574+ // to false in its `aroundInsert` callback.
575+ var persistenceContainer = dao. persistenceContainer
576+ let rowIDColumn = dao. primaryKey. rowIDColumn
577+ if let rowIDColumn {
578+ persistenceContainer [ rowIDColumn] = rowid
579+ }
457580
458- // Now that we have fetched the values we need, we could stop
459- // there. But let's make sure we fully consume the cursor
460- // anyway, until SQLITE_DONE. This is necessary, for example,
461- // for upserts in tables that are synchronized with an
462- // FTS5 table.
463- // See <https://github.com/groue/GRDB.swift/issues/1390>
464- while try cursor. next ( ) != nil { }
581+ let inserted = InsertionSuccess (
582+ rowID: rowid,
583+ rowIDColumn: rowIDColumn,
584+ persistenceContainer: persistenceContainer)
585+ return ( inserted, returned)
586+ } else {
587+ let dao = try DAO ( db, self )
588+ let statement = try dao. upsertStatement (
589+ db, onConflict: conflictTarget,
590+ updating: strategy,
591+ doUpdate: assignments,
592+ updateCondition: nil ,
593+ returning: selection)
594+ let cursor = try Row . fetchCursor ( statement)
465595
466- return ( rowid, returned)
467- }
468-
469- // Update the persistenceContainer with the inserted rowid.
470- // This allows the Record class to set its `hasDatabaseChanges` property
471- // to false in its `aroundInsert` callback.
472- var persistenceContainer = dao. persistenceContainer
473- let rowIDColumn = dao. primaryKey. rowIDColumn
474- if let rowIDColumn {
475- persistenceContainer [ rowIDColumn] = rowid
596+ // Keep cursor alive until we can process the fetched row
597+ let returned : T = try withExtendedLifetime ( cursor) { cursor in
598+ guard let row = try cursor. next ( ) else {
599+ throw DatabaseError ( message: " Insertion failed " )
600+ }
601+
602+ let returned = try decode ( row)
603+
604+ // Now that we have fetched the values we need, we could stop
605+ // there. But let's make sure we fully consume the cursor
606+ // anyway, until SQLITE_DONE. This is necessary, for example,
607+ // for upserts in tables that are synchronized with an
608+ // FTS5 table.
609+ // See <https://github.com/groue/GRDB.swift/issues/1390>
610+ while try cursor. next ( ) != nil { }
611+
612+ return returned
613+ }
614+
615+ let inserted = InsertionSuccess (
616+ rowID: 0 , // Meaningless in WITHOUT ROWID tables.
617+ rowIDColumn: nil ,
618+ persistenceContainer: dao. persistenceContainer)
619+ return ( inserted, returned)
476620 }
477-
478- let inserted = InsertionSuccess (
479- rowID: rowid,
480- rowIDColumn: rowIDColumn,
481- persistenceContainer: persistenceContainer)
482- return ( inserted, returned)
483621 }
484622}
0 commit comments