Skip to content

Commit

Permalink
feat: add support for running insert, update and delete queries alway…
Browse files Browse the repository at this point in the history
…s inside a transaction

This is a make shift feature that will be later replaced with middleware
  • Loading branch information
thetutlage committed Sep 30, 2021
1 parent 78e7a53 commit be92c9f
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 4 deletions.
6 changes: 6 additions & 0 deletions adonis-typings/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,12 @@ declare module '@ioc:Adonis/Lucid/Orm' {
$trx?: TransactionClientContract
$setOptionsAndTrx(options?: ModelAdapterOptions): void

/**
* Enable/disable managed transaction for "insert", "update" and "delete"
* queries
*/
$enableManagedTransaction(state: boolean): void

useTransaction(trx: TransactionClientContract): this
useConnection(connection: string): this

Expand Down
103 changes: 100 additions & 3 deletions src/Orm/BaseModel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,11 @@ export class BaseModel implements LucidRow {
*/
private cachedGetters: { [key: string]: CacheNode } = {}

/**
* Wrap insert, update and delete queries inside a transaction
*/
private saveInsideTransaction = false

/**
* Raises exception when mutations are performed on a delete model
*/
Expand Down Expand Up @@ -1129,6 +1134,90 @@ export class BaseModel implements LucidRow {
return !pick || pick.includes(serializeAs)
}

/**
* Peform the insert query
*/
private async performInsert() {
const Model = this.constructor as typeof BaseModel

/**
* Execute insert call when "saveInsideTransaction" is not enabled
* or the model is already using a transaction
*/
if (!this.saveInsideTransaction || this.$trx) {
return Model.$adapter.insert(this, this.prepareForAdapter(this.$attributes))
}

/**
* Create a self managed transaction and rollback/commit it
*/
this.$trx = await Model.$adapter.modelClient(this).transaction()

try {
await Model.$adapter.insert(this, this.prepareForAdapter(this.$attributes))
await this.$trx.commit()
} catch (error) {
await this.$trx.rollback()
throw error
}
}

/**
* Peform the update query
*/
private async performUpdate() {
const Model = this.constructor as typeof BaseModel

/**
* Execute insert call when "saveInsideTransaction" is not enabled
* or the model is already using a transaction
*/
if (!this.saveInsideTransaction || this.$trx) {
return Model.$adapter.update(this, this.prepareForAdapter(this.$dirty))
}

/**
* Create a self managed transaction and rollback/commit it
*/
this.$trx = await Model.$adapter.modelClient(this).transaction()

try {
await Model.$adapter.update(this, this.prepareForAdapter(this.$dirty))
await this.$trx.commit()
} catch (error) {
await this.$trx.rollback()
throw error
}
}

/**
* Perform the delete query
*/
private async performDelete() {
const Model = this.constructor as typeof BaseModel

/**
* Execute insert call when "saveInsideTransaction" is not enabled
* or the model is already using a transaction
*/
if (!this.saveInsideTransaction || this.$trx) {
return Model.$adapter.delete(this)
}

/**
* Create a self managed transaction and rollback/commit it
*/
this.$trx = await Model.$adapter.modelClient(this).transaction()

try {
await Model.$adapter.delete(this)
await this.$trx.commit()
} catch (error) {
await this.$trx.rollback()
throw error
}
}

/**
* A type only reference to the columns
*/
Expand Down Expand Up @@ -1345,6 +1434,14 @@ export class BaseModel implements LucidRow {
this.$options = options
}

/**
* Enable/disable managed transaction for "insert", "update" and "delete"
* queries
*/
public $enableManagedTransaction(state: boolean): void {
this.saveInsideTransaction = state
}

/**
* A chainable method to set transaction on the model
*/
Expand Down Expand Up @@ -1735,7 +1832,7 @@ export class BaseModel implements LucidRow {
await Model.$hooks.exec('before', 'save', this)

this.initiateAutoCreateColumns()
await Model.$adapter.insert(this, this.prepareForAdapter(this.$attributes))
await this.performInsert()

this.$hydrateOriginals()
this.$isPersisted = true
Expand Down Expand Up @@ -1764,7 +1861,7 @@ export class BaseModel implements LucidRow {
* Perform update
*/
this.initiateAutoUpdateColumns()
await Model.$adapter.update(this, this.prepareForAdapter(this.$dirty))
await this.performUpdate()
this.$hydrateOriginals()

await Model.$hooks.exec('after', 'update', this)
Expand All @@ -1781,7 +1878,7 @@ export class BaseModel implements LucidRow {

await Model.$hooks.exec('before', 'delete', this)

await Model.$adapter.delete(this)
await this.performDelete()
this.$isDeleted = true

await Model.$hooks.exec('after', 'delete', this)
Expand Down
15 changes: 14 additions & 1 deletion test-helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { FactoryModel } from '../src/Factory/FactoryModel'
import { RawQueryBuilder } from '../src/Database/QueryBuilder/Raw'
import { InsertQueryBuilder } from '../src/Database/QueryBuilder/Insert'
import { DatabaseQueryBuilder } from '../src/Database/QueryBuilder/Database'
import EventEmitter from 'events'

export const fs = new Filesystem(join(__dirname, 'tmp'))
dotenv.config()
Expand Down Expand Up @@ -474,7 +475,19 @@ export class FakeAdapter implements AdapterContract {
this._handlers[action] = handler
}

public modelClient(): any {}
public modelClient(): any {
return {
transaction() {
return {
on() {},
once() {},
removeListeners() {},
commit() {},
rollback() {},
}
},
}
}

public modelConstructorClient(): any {}

Expand Down
39 changes: 39 additions & 0 deletions test/orm/base-model.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1078,6 +1078,45 @@ test.group('Base Model | persist', (group) => {
assert.lengthOf(users, 1)
assert.equal(users[0].id.toLowerCase(), newUuid)
})

test('wrap insert/update calls inside a self managed transaction', async (assert) => {
assert.plan(3)

class User extends BaseModel {
public get $trx() {
return super.$trx
}

public set $trx(transaction) {
assert.isTrue(true)
super.$trx = transaction
}

@column({ isPrimary: true })
public id: number

@column()
public username: string

@column()
public createdAt: string

@column({ columnName: 'updated_at' })
public updatedAt: string
}

User.boot()

const user = new User()
user.username = 'virk'
user.$enableManagedTransaction(true)
await user.save()

user.username = 'nikk'
await user.save()

await user.delete()
})
})

test.group('Self assign primary key', () => {
Expand Down

0 comments on commit be92c9f

Please sign in to comment.