import { JsonConnection } from '../vn/json-connection' import { ResultSet } from './result-set' /** * Simulates a connection to a database by making asynchronous requests to a * remote REST service that returns the results in JSON format. * Using this class can perform any operation that can be done with a database, * like open/close a connection or selecion/updating queries. * * Warning! You should set a well defined dababase level privileges to use this * class or you could have a serious security hole in you application becasuse * the user can send any statement to the server. For example: DROP DATABASE */ const Flag = { NOT_NULL: 1, PRI_KEY: 2, AI: 512 | 2 | 1 } const Type = { BOOLEAN: 1, INTEGER: 3, DOUBLE: 4, STRING: 5, DATE: 8, DATE_TIME: 9 } export class Connection extends JsonConnection { static Flag = Flag static Type = Type /** * Runs a SQL query on the database. * * @param {String} sql The SQL statement * @return {ResultSet} The result */ async execSql (sql) { const json = await this.send('core/query', { sql }) const results = [] let err if (json) { try { if (json && json instanceof Array) { for (let i = 0; i < json.length; i++) { if (json[i] !== true) { const rows = json[i].data const columns = json[i].columns const data = new Array(rows.length) results.push({ data, columns, tables: json[i].tables }) for (let j = 0; j < rows.length; j++) { const row = (data[j] = {}) for (let k = 0; k < columns.length; k++) { row[columns[k].name] = rows[j][k] } } for (let j = 0; j < columns.length; j++) { let castFunc = null const col = columns[j] switch (col.type) { case Type.DATE: case Type.DATE_TIME: case Type.TIMESTAMP: castFunc = this.valueToDate break } if (castFunc !== null) { if (col.def != null) { col.def = castFunc(col.def) } for (let k = 0; k < data.length; k++) { if (data[k][col.name] != null) { data[k][col.name] = castFunc(data[k][col.name]) } } } } } else { results.push(json[i]) } } } } catch (e) { err = e } } return new ResultSet(results, err) } /** * Runs a query on the database. * * @param {String} query The SQL statement * @param {Object} params The query params * @return {ResultSet} The result */ async execQuery (query, params) { const sql = query.replace(/#\w+/g, (key) => { const value = params[key.substring(1)] return value ? this.renderValue(value) : key }) return await this.execSql(sql) } async query (query, params) { const res = await this.execQuery(query, params) return res.fetchData() } async getObject (query, params) { const res = await this.execQuery(query, params) return res.fetchObject() } async getValue (query, params) { const res = await this.execQuery(query, params) return res.fetchValue() } renderValue (v) { switch (typeof v) { case 'number': return v case 'boolean': return v ? 'TRUE' : 'FALSE' case 'string': return "'" + v.replace(this.regexp, this.replaceFunc) + "'" default: if (v instanceof Date) { if (!isNaN(v.getTime())) { const unixTime = parseInt(fixTz(v).getTime() / 1000) return 'DATE(FROM_UNIXTIME(' + unixTime + '))' } else { return '0000-00-00' } } else { return 'NULL' } } } /* * Parses a value to date. */ valueToDate (value) { return fixTz(new Date(value)) } } // TODO: Read time zone from db configuration const tz = { timeZone: 'Europe/Madrid' } const isLocal = Intl.DateTimeFormat().resolvedOptions().timeZone === tz.timeZone function fixTz (date) { if (isLocal) return date const localDate = new Date(date.toLocaleString('en-US', tz)) const hasTime = localDate.getHours() || localDate.getMinutes() || localDate.getSeconds() || localDate.getMilliseconds() if (!hasTime) { date.setHours(date.getHours() + 12) date.setHours(0, 0, 0, 0) } return date }