Skip to content

Commit

Permalink
fix: short-circuit "0 rows" queries
Browse files Browse the repository at this point in the history
E.g when `IN ()` is used.
  • Loading branch information
kirillgroshkov committed May 23, 2022
1 parent bd96744 commit 3f454e0
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 147 deletions.
14 changes: 13 additions & 1 deletion src/mysql.db.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Transform } from 'stream'
import { Readable, Transform } from 'stream'
import { promisify } from 'util'
import {
BaseCommonDB,
Expand Down Expand Up @@ -199,6 +199,12 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
_opt: MysqlDBOptions = {},
): Promise<RunQueryResult<OUT>> {
const sql = dbQueryToSQLSelect(q)
if (!sql) {
return {
rows: [],
}
}

const rows = (await this.runSQL<ROW[]>({ sql })).map(
row => _mapKeys(_filterUndefinedValues(row, true), k => mapNameFromMySQL(k)) as any,
)
Expand Down Expand Up @@ -254,6 +260,9 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
_opt: MysqlDBOptions = {},
): ReadableTyped<OUT> {
const sql = dbQueryToSQLSelect(q)
if (!sql) {
return Readable.from([])
}

if (this.cfg.logSQL) this.cfg.logger.log(`stream: ${sql}`)

Expand Down Expand Up @@ -323,6 +332,8 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
): Promise<number> {
if (!ids.length) return 0
const sql = dbQueryToSQLDelete(new DBQuery(table).filterEq('id', ids))
if (!sql) return 0

const { affectedRows } = await this.runSQL<OkPacket>({ sql })
return affectedRows
}
Expand All @@ -332,6 +343,7 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
_opt?: CommonDBOptions,
): Promise<number> {
const sql = dbQueryToSQLDelete(q)
if (!sql) return 0
const { affectedRows } = await this.runSQL<OkPacket>({ sql })
return affectedRows
}
Expand Down
41 changes: 33 additions & 8 deletions src/query.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,17 @@ import { mapNameToMySQL } from './schema/mysql.schema.util'
const MAX_PACKET_SIZE = 1024 * 1024 // 1Mb
const MAX_ROW_SIZE = 800 * 1024 // 1Mb - margin

export function dbQueryToSQLSelect(q: DBQuery<any>): string {
/**
* Returns `null` if it detects that 0 rows will be returned,
* e.g when `IN ()` (empty array) is used.
*/
export function dbQueryToSQLSelect(q: DBQuery<any>): string | null {
const tokens = selectTokens(q)

// filters
tokens.push(...whereTokens(q))
const whereTokens = getWhereTokens(q)
if (!whereTokens) return null
tokens.push(...whereTokens)

// order
tokens.push(...groupOrderTokens(q))
Expand All @@ -24,11 +30,16 @@ export function dbQueryToSQLSelect(q: DBQuery<any>): string {
return tokens.join(' ')
}

export function dbQueryToSQLDelete(q: DBQuery<any>): string {
/**
* Returns null in "0 rows" case.
*/
export function dbQueryToSQLDelete(q: DBQuery<any>): string | null {
const tokens = [`DELETE FROM`, mysql.escapeId(q.table)]

// filters
tokens.push(...whereTokens(q))
const whereTokens = getWhereTokens(q)
if (!whereTokens) return null
tokens.push(...whereTokens)

// offset/limit
tokens.push(...offsetLimitTokens(q))
Expand Down Expand Up @@ -132,16 +143,19 @@ export function insertSQLSetSingle(table: string, record: Record<any, any>): Que
}
}

export function dbQueryToSQLUpdate(q: DBQuery<any>, record: Record<any, any>): string {
export function dbQueryToSQLUpdate(q: DBQuery<any>, record: Record<any, any>): string | null {
// var sql = mysql.format('UPDATE posts SET modified = ? WHERE id = ?', [CURRENT_TIMESTAMP, 42]);
const whereTokens = getWhereTokens(q)
if (!whereTokens) return null

const tokens = [
`UPDATE`,
mysql.escapeId(q.table),
`SET`,
Object.keys(record)
.map(f => mysql.escapeId(mapNameToMySQL(f)) + ' = ?')
.join(', '),
...whereTokens(q),
...whereTokens,
]

return mysql.format(tokens.join(' '), Object.values(record))
Expand Down Expand Up @@ -200,10 +214,15 @@ const OP_MAP: Partial<Record<DBQueryFilterOperator, string>> = {
'==': '=',
}

function whereTokens(q: DBQuery): string[] {
/**
* Returns `null` for "guaranteed 0 rows" cases.
*/
function getWhereTokens(q: DBQuery): string[] | null {
if (!q._filters.length) return []

return [
let returnNull = false

const tokens = [
`WHERE`,
q._filters
.map(f => {
Expand All @@ -218,6 +237,8 @@ function whereTokens(q: DBQuery): string[] {

if (Array.isArray(f.val)) {
// special case for arrays
if (!f.val.length) returnNull = true

return `${mysql.escapeId(mapNameToMySQL(f.name as string))} IN (${mysql.escape(f.val)})`
}

Expand All @@ -229,4 +250,8 @@ function whereTokens(q: DBQuery): string[] {
})
.join(' AND '),
]

if (returnNull) return null

return tokens
}
Loading

0 comments on commit 3f454e0

Please sign in to comment.