sql-dao
Version:
database access objects for sql databases
309 lines (290 loc) • 8.32 kB
JavaScript
const mysql = require('mysql')
const DatabaseConnection = require('../DatabaseConnection')
const TableShema = require('../tableShema/TableShema')
const Column = require('../tableShema/Column')
const ColumnType = require('../tableShema/ColumnType')
/* eslint-disable no-unused-vars */
const WhereClause = require('../WhereClause')
const Join = require('../Join')
/* eslint-enable no-unused-vars */
class MySqlDatabaseConnection extends DatabaseConnection {
/**
* @returns {*} transaction
*/
async createTransaction () {
return new Promise((resolve, reject) => {
const connection = mysql.createConnection(this.config)
connection.connect((err) => {
if (err) {
reject(err)
return
}
connection.beginTransaction((err) => {
if (err) {
reject(err)
return
}
resolve(connection)
})
})
})
}
/**
* @param {*} transaction
*/
async commitTransaction (transaction) {
return new Promise((resolve, reject) => {
transaction.commit((e) => {
transaction.end()
if (e) {
reject(e)
} else {
resolve()
}
})
})
}
/**
* @param {*} transaction
*/
async rollbackTransaction (transaction) {
return new Promise((resolve, reject) => {
transaction.rollback((e) => {
transaction.end()
if (e) {
reject(e)
} else {
resolve()
}
})
})
}
/**
* @param {string} query
* @param {*} transaction
*/
async sendQuery (query, transaction = undefined) {
const isTransaction = typeof transaction === 'object'
return new Promise((resolve, reject) => {
let connection
if (isTransaction) {
connection = transaction
} else {
connection = mysql.createConnection(this.config)
connection.connect()
}
connection.query(query, (error, results, fields) => {
if (error) {
reject(error)
} else {
resolve(results)
}
})
if (!isTransaction) {
connection.end()
}
})
}
/**
* @param {string} tableName
* @param {Map<string, any>} attributeMap <column, value>
*/
createInsertQuery (tableName, attributeMap) {
const placeHolderCols = new Array(attributeMap.size).fill('??').join(',')
const placeHolderVals = new Array(attributeMap.size).fill('?').join(',')
const sql = `INSERT INTO ?? (${placeHolderCols}) VALUES(${placeHolderVals})`
let values = [tableName]
values = values.concat(Array.from(attributeMap.keys()))
values = values.concat(Array.from(attributeMap.values()))
return this.prepareQuery(sql, values)
}
/**
* @param {string} tableName
* @param {WhereClause} whereClause
* @param {Join[]} joins
*/
createFindQuery (tableName, whereClause = undefined, joins = []) {
let sql = 'SELECT ??.* FROM ??'
let values = [tableName, tableName]
for (const join of joins) {
sql += `\n${join.type} ?? ON (${join.onClause})`
values.push(join.tableName)
if (Array.isArray(join.onValues)) {
values = values.concat(join.onValues)
}
}
if (typeof whereClause === 'object') {
sql += `\nWHERE ${whereClause.clause}`
values = values.concat(whereClause.values)
}
return this.prepareQuery(sql, values)
}
/**
* @param {string} tableName
* @param {Map<string, any>} attributeMap <column, value>
* @param {WhereClause} whereClause
*/
createUpdateQuery (tableName, attributeMap, whereClause) {
let sql = `UPDATE ?? SET ${Array(attributeMap.size).fill('?? = ?').join(',')}`
let values = [tableName]
for (const [attr, value] of attributeMap.entries()) {
values.push(attr)
values.push(value)
}
if (typeof whereClause === 'object') {
sql += `\nWHERE ${whereClause.clause}`
values = values.concat(whereClause.values)
}
return this.prepareQuery(sql, values)
}
/**
* @param {string} tableName
* @param {WhereClause} whereClause
*/
createDeleteQuery (tableName, whereClause = undefined) {
let sql = 'DELETE FROM ??'
let values = [tableName]
if (typeof whereClause === 'object') {
sql += ' WHERE ' + whereClause.clause
values = values.concat(whereClause.values)
}
return this.prepareQuery(sql, values)
}
/**
* @param {string} tableName
* @param {Map<string, any>} attributeMap <column, value>
*/
createSaveQuery (tableName, attributeMap) {
const placeHolderCols = new Array(attributeMap.size).fill('??').join(',')
const placeHolderVals = new Array(attributeMap.size).fill('?').join(',')
const placeHolderUpdate = Array(attributeMap.size).fill('?? = ?').join(',')
const sql = `INSERT INTO ?? (${placeHolderCols}) VALUES(${placeHolderVals}) ON DUPLICATE KEY UPDATE ${placeHolderUpdate}`
let values = [tableName]
values = values.concat(Array.from(attributeMap.keys()))
values = values.concat(Array.from(attributeMap.values()))
for (const [attr, value] of attributeMap.entries()) {
values.push(attr)
values.push(value)
}
return this.prepareQuery(sql, values)
}
/**
* @params {*} result
* @returns {number|string}
*/
parsePrimaryKeyFromResult (result) {
if (typeof result.insertId !== 'number' || result.insertId === 0) {
throw new Error('cant parse inserted id')
}
return result.insertId
}
/**
* @params {*} result
* @returns {number|string}
*/
parseUpdatedRowsFromResult (result) {
return result.affectedRows
}
/**
* @params {*} result
* @returns {number|string}
*/
parseDeletedRowsFromResult (result) {
return result.affectedRows
}
/**
* @params {*} result
* @params {string[]} attributes
* @returns {Map<string, any>[]} <column, value>
*/
parseAttributeMapsFromResult (result, attributes) {
const maps = []
for (const row of result) {
const m = new Map()
for (const attr of attributes) {
m.set(attr, row[attr])
}
maps.push(m)
}
return maps
}
/**
* @returns {string[]}
*/
async getTableNames () {
const sql = 'SHOW TABLES'
const result = await this.sendQuery(sql)
const tableNames = []
for (const row of result) {
tableNames.push(row[Object.keys(row)[0]])
}
return tableNames
}
/**
* @param {string}
* @returns {Promise<TableShema>}
*/
async getTableShema (tableName) {
let sql = 'SHOW COLUMNS FROM ??'
sql = this.prepareQuery(sql, [tableName])
const tableShema = new TableShema()
tableShema.name = tableName
try {
const result = await this.sendQuery(sql)
for (const row of result) {
const column = new Column()
column.field = row.Field
column.type = this._resolveType(row.Type)
column.required = row.Null === 'NO' && row.Default === null && row.Extra !== 'auto_increment'
column.isPrimaryKey = row.Key === 'PRI'
if (column.isPrimaryKey) {
tableShema.primaryKey = column.field
}
tableShema.columns.push(column)
}
} catch (e) {
console.error(e)
throw new Error('error on parsing table shema')
}
return tableShema
}
/**
* @param {string} type
* @returns {ColumnType}
*/
_resolveType (type) {
const t = new ColumnType()
if (type.match(/date/) || type.match(/time/)) {
t.type = 'Date'
} else if (type.match(/int/)) {
t.type = 'integer'
} else if (type.match(/float/)) {
t.type = 'float'
} else if (type.match(/double/)) {
t.type = 'double'
} else if (type.match(/decimal/)) {
t.type = 'double'
} else {
t.type = 'string'
const m = type.match(/\(([0-9]+)\)/)
if (m) {
t.max = parseInt(m[1])
}
}
if (['integer', 'float', 'double'].indexOf(t.type) >= 0) {
if (type.match(/unsigned/)) {
t.min = 0
}
}
return t
}
/**
* @param {string} query placeholders are ? and ?? like in mysql lib
* @param {array} values
* @returns {string}
*/
prepareQuery (query, values) {
return mysql.format(query, values)
}
}
module.exports = MySqlDatabaseConnection