Skip to content

Commit

Permalink
create beginPrepared function (#628)
Browse files Browse the repository at this point in the history
* create beginPrepared function

* change implementation to new method

* add prepare method type to TransactionSql

* add documentations and test

* fix test

* enable prepared transactions in the bootstrap script

* enable prepared transactions in the github actions setup file

* fix github actions

* fix github actions yml file
  • Loading branch information
shayan-shojaei committed Jul 2, 2023
1 parent e546ac0 commit 8f6f4e3
Show file tree
Hide file tree
Showing 12 changed files with 117 additions and 4 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Expand Up @@ -35,6 +35,7 @@ jobs:
sudo apt-get -y install "postgresql-${{ matrix.postgres }}"
sudo cp ./tests/pg_hba.conf /etc/postgresql/${{ matrix.postgres }}/main/pg_hba.conf
sudo sed -i 's/.*wal_level.*/wal_level = logical/' /etc/postgresql/${{ matrix.postgres }}/main/postgresql.conf
sudo sed -i 's/.*max_prepared_transactions.*/max_prepared_transactions = 100/' /etc/postgresql/${{ matrix.postgres }}/main/postgresql.conf
sudo sed -i 's/.*ssl = .*/ssl = on/' /etc/postgresql/${{ matrix.postgres }}/main/postgresql.conf
openssl req -new -x509 -nodes -days 365 -text -subj "/CN=localhost" -extensions v3_req -config <(cat /etc/ssl/openssl.cnf <(printf "\n[v3_req]\nbasicConstraints=critical,CA:TRUE\nkeyUsage=nonRepudiation,digitalSignature,keyEncipherment\nsubjectAltName=DNS:localhost")) -keyout server.key -out server.crt
sudo cp server.key /etc/postgresql/${{ matrix.postgres }}/main/server.key
Expand Down
20 changes: 20 additions & 0 deletions README.md
Expand Up @@ -637,6 +637,26 @@ sql.begin('read write', async sql => {
})
```


#### PREPARE `await sql.prepare([name]) -> fn()`

Indicates that the transactions should be prepared using the `PREPARED TRANASCTION [NAME]` statement
instead of being committed.

```js
sql.begin('read write', async sql => {
const [user] = await sql`
insert into users (
name
) values (
'Murray'
)
`

await sql.prepare('tx1')
})
```

Do note that you can often achieve the same result using [`WITH` queries (Common Table Expressions)](https://www.postgresql.org/docs/current/queries-with.html) instead of using transactions.

## Data Transformation
Expand Down
12 changes: 11 additions & 1 deletion cjs/src/index.js
Expand Up @@ -235,6 +235,7 @@ function Postgres(a, b) {
const queries = Queue()
let savepoints = 0
, connection
let transactionId = null

try {
await sql.unsafe('begin ' + options.replace(/[^a-z ]/ig, ''), [], { onexecute }).execute()
Expand All @@ -246,6 +247,7 @@ function Postgres(a, b) {
async function scope(c, fn, name) {
const sql = Sql(handler)
sql.savepoint = savepoint
sql.prepare = prepare
let uncaughtError
, result

Expand All @@ -266,7 +268,11 @@ function Postgres(a, b) {
throw e instanceof PostgresError && e.code === '25P02' && uncaughtError || e
}

!name && await sql`commit`
if (transactionId) {
!name && await sql.unsafe(`prepare transaction '${transactionId}'`)
}else{
!name && await sql`commit`
}
return result

function savepoint(name, fn) {
Expand All @@ -285,6 +291,9 @@ function Postgres(a, b) {
}
}

async function prepare(name) {
transactionId = name
}
function onexecute(c) {
connection = c
move(c, reserved)
Expand All @@ -294,6 +303,7 @@ function Postgres(a, b) {
}
}


function move(c, queue) {
c.queue.remove(c)
queue.push(c)
Expand Down
13 changes: 13 additions & 0 deletions cjs/tests/index.js
Expand Up @@ -238,6 +238,19 @@ t('Savepoint returns Result', async() => {
return [1, result[0].x]
})

t('Prepared transaction', async() => {
await sql`create table test (a int)`

await sql.begin(async sql => {
await sql`insert into test values(1)`
await sql.prepare('tx1')
})

await sql.unsafe("commit prepared 'tx1'")

return ['1', (await sql`select count(1) from test`)[0].count, await sql`drop table test`]
})

t('Transaction requests are executed implicitly', async() => {
const sql = postgres({ debug: true, idle_timeout: 1, fetch_types: false })
return [
Expand Down
20 changes: 20 additions & 0 deletions deno/README.md
Expand Up @@ -633,6 +633,26 @@ sql.begin('read write', async sql => {
})
```


#### PREPARE `await sql.prepare([name]) -> fn()`

Indicates that the transactions should be prepared using the `PREPARED TRANASCTION [NAME]` statement
instead of being committed.

```js
sql.begin('read write', async sql => {
const [user] = await sql`
insert into users (
name
) values (
'Murray'
)
`

await sql.prepare('tx1')
})
```

Do note that you can often achieve the same result using [`WITH` queries (Common Table Expressions)](https://www.postgresql.org/docs/current/queries-with.html) instead of using transactions.

## Data Transformation
Expand Down
12 changes: 11 additions & 1 deletion deno/src/index.js
Expand Up @@ -236,6 +236,7 @@ function Postgres(a, b) {
const queries = Queue()
let savepoints = 0
, connection
let transactionId = null

try {
await sql.unsafe('begin ' + options.replace(/[^a-z ]/ig, ''), [], { onexecute }).execute()
Expand All @@ -247,6 +248,7 @@ function Postgres(a, b) {
async function scope(c, fn, name) {
const sql = Sql(handler)
sql.savepoint = savepoint
sql.prepare = prepare
let uncaughtError
, result

Expand All @@ -267,7 +269,11 @@ function Postgres(a, b) {
throw e instanceof PostgresError && e.code === '25P02' && uncaughtError || e
}

!name && await sql`commit`
if (transactionId) {
!name && await sql.unsafe(`prepare transaction '${transactionId}'`)
}else{
!name && await sql`commit`
}
return result

function savepoint(name, fn) {
Expand All @@ -286,6 +292,9 @@ function Postgres(a, b) {
}
}

async function prepare(name) {
transactionId = name
}
function onexecute(c) {
connection = c
move(c, reserved)
Expand All @@ -295,6 +304,7 @@ function Postgres(a, b) {
}
}


function move(c, queue) {
c.queue.remove(c)
queue.push(c)
Expand Down
13 changes: 13 additions & 0 deletions deno/tests/index.js
Expand Up @@ -240,6 +240,19 @@ t('Savepoint returns Result', async() => {
return [1, result[0].x]
})

t('Prepared transaction', async() => {
await sql`create table test (a int)`

await sql.begin(async sql => {
await sql`insert into test values(1)`
await sql.prepare('tx1')
})

await sql.unsafe("commit prepared 'tx1'")

return ['1', (await sql`select count(1) from test`)[0].count, await sql`drop table test`]
})

t('Transaction requests are executed implicitly', async() => {
const sql = postgres({ debug: true, idle_timeout: 1, fetch_types: false })
return [
Expand Down
2 changes: 2 additions & 0 deletions deno/types/index.d.ts
Expand Up @@ -698,6 +698,8 @@ declare namespace postgres {
interface TransactionSql<TTypes extends Record<string, unknown> = {}> extends Sql<TTypes> {
savepoint<T>(cb: (sql: TransactionSql<TTypes>) => T | Promise<T>): Promise<UnwrapPromiseArray<T>>;
savepoint<T>(name: string, cb: (sql: TransactionSql<TTypes>) => T | Promise<T>): Promise<UnwrapPromiseArray<T>>;

prepare<T>(name: string): Promise<UnwrapPromiseArray<T>>;
}
}

Expand Down
12 changes: 11 additions & 1 deletion src/index.js
Expand Up @@ -235,6 +235,7 @@ function Postgres(a, b) {
const queries = Queue()
let savepoints = 0
, connection
let transactionId = null

try {
await sql.unsafe('begin ' + options.replace(/[^a-z ]/ig, ''), [], { onexecute }).execute()
Expand All @@ -246,6 +247,7 @@ function Postgres(a, b) {
async function scope(c, fn, name) {
const sql = Sql(handler)
sql.savepoint = savepoint
sql.prepare = prepare
let uncaughtError
, result

Expand All @@ -266,7 +268,11 @@ function Postgres(a, b) {
throw e instanceof PostgresError && e.code === '25P02' && uncaughtError || e
}

!name && await sql`commit`
if (transactionId) {
!name && await sql.unsafe(`prepare transaction '${transactionId}'`)
}else{
!name && await sql`commit`
}
return result

function savepoint(name, fn) {
Expand All @@ -285,6 +291,9 @@ function Postgres(a, b) {
}
}

async function prepare(name) {
transactionId = name
}
function onexecute(c) {
connection = c
move(c, reserved)
Expand All @@ -294,6 +303,7 @@ function Postgres(a, b) {
}
}


function move(c, queue) {
c.queue.remove(c)
queue.push(c)
Expand Down
1 change: 0 additions & 1 deletion tests/bootstrap.js
Expand Up @@ -14,7 +14,6 @@ exec('createdb', ['postgres_js_test'])
exec('psql', ['-c', 'grant all on database postgres_js_test to postgres_js_test'])
exec('psql', ['-c', 'alter database postgres_js_test owner to postgres_js_test'])


export function exec(cmd, args) {
const { stderr } = spawnSync(cmd, args, { stdio: 'pipe', encoding: 'utf8' })
if (stderr && !stderr.includes('already exists') && !stderr.includes('does not exist'))
Expand Down
13 changes: 13 additions & 0 deletions tests/index.js
Expand Up @@ -238,6 +238,19 @@ t('Savepoint returns Result', async() => {
return [1, result[0].x]
})

t('Prepared transaction', async() => {
await sql`create table test (a int)`

await sql.begin(async sql => {
await sql`insert into test values(1)`
await sql.prepare('tx1')
})

await sql.unsafe("commit prepared 'tx1'")

return ['1', (await sql`select count(1) from test`)[0].count, await sql`drop table test`]
})

t('Transaction requests are executed implicitly', async() => {
const sql = postgres({ debug: true, idle_timeout: 1, fetch_types: false })
return [
Expand Down
2 changes: 2 additions & 0 deletions types/index.d.ts
Expand Up @@ -696,6 +696,8 @@ declare namespace postgres {
interface TransactionSql<TTypes extends Record<string, unknown> = {}> extends Sql<TTypes> {
savepoint<T>(cb: (sql: TransactionSql<TTypes>) => T | Promise<T>): Promise<UnwrapPromiseArray<T>>;
savepoint<T>(name: string, cb: (sql: TransactionSql<TTypes>) => T | Promise<T>): Promise<UnwrapPromiseArray<T>>;

prepare<T>(name: string): Promise<UnwrapPromiseArray<T>>;
}
}

Expand Down

0 comments on commit 8f6f4e3

Please sign in to comment.