2
0
mirror of https://github.com/frappe/books.git synced 2024-12-27 04:32:41 +00:00
books/backends/sqlite.js

315 lines
8.8 KiB
JavaScript
Raw Normal View History

2018-01-16 06:09:17 +00:00
const frappe = require('frappejs');
2018-01-12 12:25:07 +00:00
const sqlite3 = require('sqlite3').verbose();
const Database = require('./database');
2019-11-20 09:38:32 +00:00
const errors = require('frappejs/common/errors');
2018-02-07 13:23:52 +00:00
const debug = false;
2018-01-12 12:25:07 +00:00
2019-11-20 09:38:32 +00:00
class SqliteDatabase extends Database {
constructor({ dbPath }) {
super();
this.dbPath = dbPath;
}
connect(dbPath) {
if (dbPath) {
this.dbPath = dbPath;
2018-01-31 13:04:46 +00:00
}
2019-01-12 12:10:52 +00:00
return new Promise((resolve, reject) => {
this.conn = new sqlite3.Database(this.dbPath, (err) => {
if (err) {
console.log(err);
reject(err);
return;
}
if (debug) {
this.conn.on('trace', (trace) => console.log(trace));
2018-01-31 13:04:46 +00:00
}
this.run('PRAGMA foreign_keys=ON').then(resolve);
});
});
}
2018-01-31 13:04:46 +00:00
async tableExists(table) {
const name = await this.sql(`SELECT name FROM sqlite_master WHERE type='table' AND name='${table}'`);
return (name && name.length) ? true : false;
}
2018-01-31 13:04:46 +00:00
async addForeignKeys(doctype, newForeignKeys) {
await this.run('PRAGMA foreign_keys=OFF');
await this.run('BEGIN TRANSACTION');
2018-02-20 09:53:38 +00:00
const tempName = 'TEMP' + doctype
2018-03-05 16:45:21 +00:00
// create temp table
await this.createTable(doctype, tempName);
2018-03-05 16:45:21 +00:00
const columns = (await this.getTableColumns(tempName)).join(', ');
2018-02-20 09:53:38 +00:00
// copy from old to new table
await this.run(`INSERT INTO ${tempName} (${columns}) SELECT ${columns} from ${doctype}`);
2018-02-20 09:53:38 +00:00
// drop old table
await this.run(`DROP TABLE ${doctype}`);
2018-02-20 09:53:38 +00:00
// rename new table
await this.run(`ALTER TABLE ${tempName} RENAME TO ${doctype}`);
2018-02-20 09:53:38 +00:00
await this.run('COMMIT');
await this.run('PRAGMA foreign_keys=ON');
}
2018-02-20 09:53:38 +00:00
removeColumns() {
// pass
}
2018-03-05 16:45:21 +00:00
async runCreateTableQuery(doctype, columns, indexes) {
const query = `CREATE TABLE IF NOT EXISTS ${doctype} (
2018-02-20 09:53:38 +00:00
${columns.join(", ")} ${indexes.length ? (", " + indexes.join(", ")) : ''})`;
2018-01-12 12:25:07 +00:00
return await this.run(query);
}
2018-02-23 16:17:55 +00:00
updateColumnDefinition(field, columns, indexes) {
let def = this.getColumnDefinition(field);
2018-02-23 16:17:55 +00:00
columns.push(def);
if (field.fieldtype === 'Link' && field.target) {
2019-10-19 14:33:08 +00:00
let meta = frappe.getMeta(field.target);
indexes.push(`FOREIGN KEY (${field.fieldname}) REFERENCES ${meta.getBaseDocType()} ON UPDATE CASCADE ON DELETE RESTRICT`);
2018-01-31 13:04:46 +00:00
}
}
getColumnDefinition(field) {
let defaultValue = field.default;
if (typeof defaultValue === 'string') {
defaultValue = `'${defaultValue}'`
}
let def = [
field.fieldname,
this.typeMap[field.fieldtype],
field.fieldname === 'name' ? 'PRIMARY KEY NOT NULL' : '',
field.required ? 'NOT NULL' : '',
field.default ? `DEFAULT ${defaultValue}` : ''
].join(' ');
return def;
}
async getTableColumns(doctype) {
return (await this.sql(`PRAGMA table_info(${doctype})`)).map(d => d.name);
}
async getForeignKeys(doctype) {
return (await this.sql(`PRAGMA foreign_key_list(${doctype})`)).map(d => d.from);
}
async runAddColumnQuery(doctype, field, values) {
await this.run(`ALTER TABLE ${doctype} ADD COLUMN ${this.getColumnDefinition(field)}`, values);
}
getOne(doctype, name, fields = '*') {
let meta = frappe.getMeta(doctype);
let baseDoctype = meta.getBaseDocType();
fields = this.prepareFields(fields);
return new Promise((resolve, reject) => {
this.conn.get(`select ${fields} from ${baseDoctype}
2018-01-12 12:25:07 +00:00
where name = ?`, name,
(err, row) => {
resolve(row || {});
2018-01-31 13:04:46 +00:00
});
});
}
2018-01-31 13:04:46 +00:00
async insertOne(doctype, doc) {
let fields = this.getKeys(doctype);
let placeholders = fields.map(d => '?').join(', ');
2018-01-31 13:04:46 +00:00
if (!doc.name) {
doc.name = frappe.getRandomString();
}
2018-02-01 11:07:36 +00:00
return await this.run(`insert into ${doctype}
2018-02-01 11:07:36 +00:00
(${fields.map(field => field.fieldname).join(", ")})
values (${placeholders})`, this.getFormattedValues(fields, doc));
}
2018-01-12 12:25:07 +00:00
async updateOne(doctype, doc) {
let fields = this.getKeys(doctype);
let assigns = fields.map(field => `${field.fieldname} = ?`);
let values = this.getFormattedValues(fields, doc);
2018-01-31 12:56:21 +00:00
// additional name for where clause
values.push(doc.name);
2018-01-31 12:56:21 +00:00
return await this.run(`update ${doctype}
2018-01-12 12:25:07 +00:00
set ${assigns.join(", ")} where name=?`, values);
}
2018-01-31 13:04:46 +00:00
async runDeleteOtherChildren(field, added) {
// delete other children
// `delete from doctype where parent = ? and name not in (?, ?, ?)}`
await this.run(`delete from ${field.childtype}
where
parent = ? and
name not in (${added.slice(1).map(d => '?').join(', ')})`, added);
}
async deleteOne(doctype, name) {
return await this.run(`delete from ${doctype} where name=?`, name);
}
async deleteChildren(parenttype, parent) {
await this.run(`delete from ${parenttype} where parent=?`, parent);
}
async deleteSingleValues(name) {
await frappe.db.run('delete from SingleValue where parent=?', name)
}
2019-10-05 21:46:14 +00:00
async rename(doctype, oldName, newName) {
let meta = frappe.getMeta(doctype);
let baseDoctype = meta.getBaseDocType();
2019-10-05 21:46:14 +00:00
await frappe.db.run(`update ${baseDoctype} set name = ? where name = ?`, [newName, oldName]);
await frappe.db.commit();
}
async setValues(doctype, name, fieldValuePair) {
const meta = frappe.getMeta(doctype);
const baseDoctype = meta.getBaseDocType();
const validFields = this.getKeys(doctype);
const validFieldnames = validFields.map(df => df.fieldname);
const fieldsToUpdate = Object.keys(fieldValuePair)
.filter(fieldname => validFieldnames.includes(fieldname))
// assignment part of query
const assigns = fieldsToUpdate.map(fieldname => `${fieldname} = ?`);
// values
const values = fieldsToUpdate.map(fieldname => {
const field = meta.getField(fieldname);
const value = fieldValuePair[fieldname];
return this.getFormattedValue(field, value);
});
// additional name for where clause
values.push(name);
return await this.run(`update ${baseDoctype}
set ${assigns.join(', ')} where name=?`, values);
}
getAll({ doctype, fields, filters, start, limit, orderBy = 'modified', groupBy, order = 'desc' } = {}) {
let meta = frappe.getMeta(doctype);
let baseDoctype = meta.getBaseDocType();
if (!fields) {
fields = meta.getKeywordFields();
2018-01-31 13:04:46 +00:00
}
if (typeof fields === 'string') {
fields = [fields];
2018-02-01 11:07:36 +00:00
}
if (meta.filters) {
2019-10-19 14:33:23 +00:00
filters = Object.assign({}, filters, meta.filters);
}
2018-02-01 11:07:36 +00:00
2019-11-21 19:21:56 +00:00
let conditions = this.getFilterConditions(filters);
let query = `select ${fields.join(", ")}
from ${baseDoctype}
${conditions.conditions ? "where" : ""} ${conditions.conditions}
${groupBy ? ("group by " + groupBy.join(', ')) : ""}
${orderBy ? ("order by " + orderBy) : ""} ${orderBy ? (order || "asc") : ""}
${limit ? ("limit " + limit) : ""} ${start ? ("offset " + start) : ""}`;
return this.sql(query, conditions.values);
}
run(query, params) {
return new Promise((resolve, reject) => {
this.conn.run(query, params, (err) => {
if (err) {
2019-01-12 12:10:52 +00:00
console.error('Error in sql:', query);
2019-11-20 09:38:32 +00:00
let Error = this.getError(err);
2019-11-15 07:44:45 +00:00
reject(new Error());
} else {
resolve();
2018-01-31 13:04:46 +00:00
}
});
});
}
sql(query, params) {
2019-01-12 12:10:52 +00:00
return new Promise((resolve, reject) => {
this.conn.all(query, params, (err, rows) => {
2019-01-12 12:10:52 +00:00
if (err) {
console.error('Error in sql:', query);
reject(err)
}
resolve(rows);
});
});
}
async commit() {
try {
await this.run('commit');
} catch (e) {
2019-11-20 09:38:32 +00:00
if (e.name !== 'CannotCommitError') {
throw e;
}
2018-01-31 13:04:46 +00:00
}
}
initTypeMap() {
this.typeMap = {
2019-10-26 14:46:34 +00:00
'AutoComplete': 'text'
, 'Currency': 'real'
, 'Int': 'integer'
, 'Float': 'real'
, 'Percent': 'real'
, 'Check': 'integer'
, 'Small Text': 'text'
, 'Long Text': 'text'
, 'Code': 'text'
, 'Text Editor': 'text'
, 'Date': 'text'
, 'Datetime': 'text'
, 'Time': 'text'
, 'Text': 'text'
, 'Data': 'text'
, 'Link': 'text'
, 'DynamicLink': 'text'
, 'Password': 'text'
, 'Select': 'text'
, 'Read Only': 'text'
, 'File': 'text'
, 'Attach': 'text'
2019-11-15 07:44:45 +00:00
, 'AttachImage': 'text'
, 'Signature': 'text'
, 'Color': 'text'
, 'Barcode': 'text'
, 'Geolocation': 'text'
2018-01-31 13:04:46 +00:00
}
}
2019-11-15 07:44:45 +00:00
2019-11-20 09:38:32 +00:00
getError(err) {
if (err.message.includes('FOREIGN KEY')) {
return frappe.errors.LinkValidationError;
}
if (err.message.includes('SQLITE_ERROR: cannot commit')) {
return SqliteDatabase.CannotCommitError;
}
2019-11-15 07:44:45 +00:00
return {
19: frappe.errors.DuplicateEntryError
2019-11-20 09:38:32 +00:00
}[err.errno] || Error;
}
}
SqliteDatabase.CannotCommitError = class CannotCommitError extends errors.DatabaseError {
constructor(message) {
super(message);
this.name = 'CannotCommitError';
2019-11-15 07:44:45 +00:00
}
2018-01-12 12:25:07 +00:00
}
2019-11-20 09:38:32 +00:00
module.exports = SqliteDatabase;