Fix a possible transaction/single query race in PgSQL wrapper, allow SQL.Text in update
parent
5ffc8b283d
commit
bc475535d7
|
@ -1,6 +1,6 @@
|
||||||
// Простенький "селект билдер" по мотивам MediaWiki-овского, успешно юзаю подобный в PHP уже лет 8
|
// Простенький "селект билдер" по мотивам MediaWiki-овского, успешно юзаю подобный в PHP уже лет 8
|
||||||
// (c) Виталий Филиппов, 2019-2021
|
// (c) Виталий Филиппов, 2019-2021
|
||||||
// Версия 2021-09-16
|
// Версия 2021-11-24
|
||||||
|
|
||||||
// В PHP, правда, прикольнее - там в массиве можно смешивать строковые и численные ключи,
|
// В PHP, правда, прикольнее - там в массиве можно смешивать строковые и численные ключи,
|
||||||
// благодаря чему можно писать $where = [ 't1.a=t2.a', 't2.b' => [ 1, 2, 3 ] ]
|
// благодаря чему можно писать $where = [ 't1.a=t2.a', 't2.b' => [ 1, 2, 3 ] ]
|
||||||
|
@ -213,8 +213,15 @@ function where_in(key, values, result, bind, for_where)
|
||||||
{
|
{
|
||||||
throw new Error('IN syntax can only be used inside WHERE');
|
throw new Error('IN syntax can only be used inside WHERE');
|
||||||
}
|
}
|
||||||
result.push(key+' = ?');
|
if (values[0] instanceof Text)
|
||||||
bind.push(values[0]);
|
{
|
||||||
|
push_text(result, bind, new Text(key+' = '+values[0].sql, values[0].bind));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.push(key+' = ?');
|
||||||
|
bind.push(values[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -236,6 +243,18 @@ function where_in(key, values, result, bind, for_where)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function push_text(where, bind, v)
|
||||||
|
{
|
||||||
|
if (v instanceof Text)
|
||||||
|
{
|
||||||
|
where.push(v.sql);
|
||||||
|
for (let i = 0; i < v.bind.length; i++)
|
||||||
|
bind.push(v.bind[i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
where.push(v);
|
||||||
|
}
|
||||||
|
|
||||||
// fields: one of:
|
// fields: one of:
|
||||||
// - string: 'a=b AND c=d'
|
// - string: 'a=b AND c=d'
|
||||||
// - array: [ 'a=b', [ 'a=? or b=?', 1, 2 ], [ 'a', [ 1, 2 ] ] ]
|
// - array: [ 'a=b', [ 'a=? or b=?', 1, 2 ], [ 'a', [ 1, 2 ] ] ]
|
||||||
|
@ -254,10 +273,14 @@ function where_or_set(fields, for_where)
|
||||||
for (let k in fields)
|
for (let k in fields)
|
||||||
{
|
{
|
||||||
let v = fields[k];
|
let v = fields[k];
|
||||||
v = v instanceof Array ? v : [ v ];
|
|
||||||
if (/^\d+$/.exec(k))
|
if (/^\d+$/.exec(k))
|
||||||
{
|
{
|
||||||
if (v.length == 0 || typeof v[0] !== 'string')
|
if (!(v instanceof Array))
|
||||||
|
{
|
||||||
|
push_text(where, bind, v);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (v.length == 0 || typeof v[0] !== 'string')
|
||||||
{
|
{
|
||||||
// invalid value
|
// invalid value
|
||||||
continue;
|
continue;
|
||||||
|
@ -265,7 +288,7 @@ function where_or_set(fields, for_where)
|
||||||
else if (v.length == 1)
|
else if (v.length == 1)
|
||||||
{
|
{
|
||||||
// [ text ] or just text
|
// [ text ] or just text
|
||||||
where.push(v[0]);
|
push_text(where, bind, v[0]);
|
||||||
}
|
}
|
||||||
else if (v[0].indexOf('?') >= 0)
|
else if (v[0].indexOf('?') >= 0)
|
||||||
{
|
{
|
||||||
|
@ -288,6 +311,10 @@ function where_or_set(fields, for_where)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (!(v instanceof Array))
|
||||||
|
{
|
||||||
|
v = [ v ];
|
||||||
|
}
|
||||||
if (k.indexOf('?') >= 0 || v.length == 0)
|
if (k.indexOf('?') >= 0 || v.length == 0)
|
||||||
{
|
{
|
||||||
// { expr: [ bind ] }
|
// { expr: [ bind ] }
|
||||||
|
@ -701,7 +728,7 @@ class Connection extends ConnectionBase
|
||||||
|
|
||||||
async begin()
|
async begin()
|
||||||
{
|
{
|
||||||
if (this.in_transaction)
|
while (this.in_transaction)
|
||||||
{
|
{
|
||||||
// Если уже кто-то активен - ждём его
|
// Если уже кто-то активен - ждём его
|
||||||
await new Promise((resolve, reject) => this.transaction_queue.push(resolve));
|
await new Promise((resolve, reject) => this.transaction_queue.push(resolve));
|
||||||
|
@ -711,18 +738,39 @@ class Connection extends ConnectionBase
|
||||||
return this.in_transaction;
|
return this.in_transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async txn(cb)
|
||||||
|
{
|
||||||
|
let r;
|
||||||
|
const txn = await this.begin();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
r = await cb(txn);
|
||||||
|
await txn.commit();
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
await txn.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
async query(sql, bind)
|
async query(sql, bind)
|
||||||
{
|
{
|
||||||
if (sql.length == 5 && sql.toLowerCase() == 'begin')
|
if (sql.length == 5 && sql.toLowerCase() == 'begin')
|
||||||
{
|
{
|
||||||
throw new Error('Do not use transactions in asynchronous code directly!');
|
throw new Error('Do not use transactions in asynchronous code directly!');
|
||||||
}
|
}
|
||||||
if (this.in_transaction)
|
while (this.in_transaction)
|
||||||
{
|
{
|
||||||
// Если уже кто-то активен - ждём его
|
// Если уже кто-то активен - ждём его
|
||||||
await new Promise((resolve, reject) => this.transaction_queue.push(resolve));
|
await new Promise((resolve, reject) => this.transaction_queue.push(resolve));
|
||||||
}
|
}
|
||||||
|
if (!this.dbh)
|
||||||
|
await this.connect();
|
||||||
|
this.in_transaction = true;
|
||||||
const r = await this._query(sql, bind);
|
const r = await this._query(sql, bind);
|
||||||
|
this.in_transaction = null;
|
||||||
// Если есть ещё кто-то в очереди - пусть проходит
|
// Если есть ещё кто-то в очереди - пусть проходит
|
||||||
this._next_txn();
|
this._next_txn();
|
||||||
return r;
|
return r;
|
||||||
|
@ -734,7 +782,7 @@ class Connection extends ConnectionBase
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.in_transaction)
|
while (this.in_transaction)
|
||||||
{
|
{
|
||||||
// Если уже кто-то активен - ждём его
|
// Если уже кто-то активен - ждём его
|
||||||
await new Promise((resolve, reject) => this.transaction_queue.push(resolve));
|
await new Promise((resolve, reject) => this.transaction_queue.push(resolve));
|
||||||
|
@ -753,9 +801,6 @@ class Connection extends ConnectionBase
|
||||||
|
|
||||||
async _query(sql, bind)
|
async _query(sql, bind)
|
||||||
{
|
{
|
||||||
const do_lock = !this.in_transaction;
|
|
||||||
if (!this.dbh)
|
|
||||||
await this.connect();
|
|
||||||
if (this.in_transaction && this.connection_lost)
|
if (this.in_transaction && this.connection_lost)
|
||||||
{
|
{
|
||||||
this._next_txn();
|
this._next_txn();
|
||||||
|
@ -770,8 +815,6 @@ class Connection extends ConnectionBase
|
||||||
{
|
{
|
||||||
start_time = Date.now();
|
start_time = Date.now();
|
||||||
}
|
}
|
||||||
if (do_lock)
|
|
||||||
this._in_transaction = true;
|
|
||||||
const r = await this.dbh.query(sql);
|
const r = await this.dbh.query(sql);
|
||||||
if (this.config.log_queries)
|
if (this.config.log_queries)
|
||||||
{
|
{
|
||||||
|
@ -828,6 +871,11 @@ class Transaction extends ConnectionBase
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async txn(cb)
|
||||||
|
{
|
||||||
|
return await cb(this);
|
||||||
|
}
|
||||||
|
|
||||||
async commit()
|
async commit()
|
||||||
{
|
{
|
||||||
if (this.nested > 0)
|
if (this.nested > 0)
|
||||||
|
@ -873,6 +921,7 @@ class Transaction extends ConnectionBase
|
||||||
module.exports = {
|
module.exports = {
|
||||||
select_builder,
|
select_builder,
|
||||||
where_builder,
|
where_builder,
|
||||||
|
where_or_set,
|
||||||
quote,
|
quote,
|
||||||
quote_into: _inline,
|
quote_into: _inline,
|
||||||
select,
|
select,
|
||||||
|
|
Loading…
Reference in New Issue