-
Notifications
You must be signed in to change notification settings - Fork 3
/
table.js
240 lines (224 loc) · 6.92 KB
/
table.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
const Query = require('./query')
const Parameters = require('./parameters')
const Cache = require('./cache')
/**
* Represents a table.
*
* A table will be initialized after first crud operation.
* This can be an existing one or a new one.
*
* Missing fields specified in select / where clause are atomatically created.
* Missing indexes specified in where clause are automatically created.
*
* @property {string[]} column column names
* @property {string[]} indexes index names
* @property {string} name table name
* @property {Object} db sqlite-async connection instance
* @property {bool} initialized is true if table exists, columns are loaded and indexes are loaded
*/
class Table {
constructor(db, name) {
this.db = db
this.name = name
this.indexes = []
this.columns = []
this.initialized = false
this.cache = new Cache(2)
}
/**
* Creates new record. Supported field types:
* - string
* - integer
* - boolean will be converted to int
* - date will be converted to string
*
* @example
* console.info('Created pk', await this.insert({'stringColumn': 'value', 'numberColumn': 5}})
*
* @param {Object} {'columnName':'value'}
* @returns {number} primary key of created record
*/
insert(data) {
const query = new Query.InsertQuery(this, data)
return query.execute().then((primaryKey) => {
this.cache.flush()
return primaryKey
})
}
/**
* Find multiple records
*
* @example
* await this.find({
* 'id__gte': 5,
* 'name__like': '%John',
* '_orderBy': '-id',
* '_limit': 50,
* '_offset': 10
* })
* @param {Object} [where=null] - E.g: {'id__like': '%a%'}
* @param {Array} [columnNames=null] - E.g: ['id', 'name']
* @returns {Object[]} Array of found records
*/
find(where, columnNames) {
return this.cache.get(['find', columnNames, where], () => {
const query = new Query.SelectQuery(this, columnNames, where)
return query.execute()
})
}
/**
* Find one record
*
* @example
* await this.findOne({
* 'name': 'John'
* }))
*
* @param {Object} [where={}] - E.g: {'id': 1}
* @param {string[]} [columnNames=null]
* @returns {Object} Record
* @returns {null} if no matches
*/
findOne(where, columnNames) {
const whereCriteria = where || {}
whereCriteria._limit = 1
return this.cache.get(['findOne', columnNames, whereCriteria], () => {
const query = new Query.SelectQuery(this, columnNames, where)
return query.execute().then(rows => {
return rows[0]
})
})
}
/**
*
* @example <caption>Update with column name array as where clause</caption>
* const changedRecordCount = await this.update({
* 'id': 1
* 'name': 'John'
* },['id'])
*
* @example <caption>Update with string as where clause</caption>
* const changedRecordCount = await this.update({
* 'id': 1
* 'name': 'John'
* },'id')
*
* @example <caption>Update with object as where clause</caption>
* const changedRecordCount = await this.update({
* 'id': 1
* 'name': 'John'
* },{'id': 1})
*
* @example <caption>Update without where clause</caption>
* // Update with no where clause
* const changedRecordCount = await this.update({
* 'id': 1
* 'name': 'John'
* })
*
* @param {Object} data record
* @param {Object} where values that should match
* @param {string[]} where field names of values that should match
*/
update(data, where) {
// returns update count
const query = new Query.UpdateQuery(this, data, where)
return query.execute().then((updatedCount) => {
this.cache.flush()
return updatedCount
})
}
delete(where) {
// returns deleted count
const query = new Query.DeleteQuery(this, null, where)
return query.execute().then((deletedCount) => {
this.cache.flush()
return deletedCount
})
}
upsert(data, where) {
// returns true if updated, else new primary key
return this.update(data, where).then(changeCount => {
return changeCount ? true : this.insert(data)
}).then((result) => {
this.cache.flush()
return result
})
}
count(where) {
return this.cache.get(['count', where], () => {
const query = new Query.CountQuery(this, null, where)
return query.execute()
})
}
ensureIndex(names) {
return new Promise((resolve, reject) => {
if (names.length < 2) {
return resolve(false)
}
names.sort()
const indexName = 'idx_' + names.join('_')
if (this.indexes.indexOf(indexName) !== -1) {
return resolve(false)
}
const nameString = names.join(',')
const query = [
'CREATE INDEX',
indexName,
'ON',
`"${this.name}"`,
`(${nameString})`
].join(' ')
this.indexes.push(indexName)
this.db.run(query, []).then(resolve)
})
}
async ensure(obj) {
if (!this.initialized) {
this.initialized = true
await this.createTable()
this.columns = await this.getColumns()
this.indexes = await this.getIndexes()
}
obj = obj || {}
const params = new Parameters(obj)
const promises = []
params.keys.filter(
key => this.columns.indexOf(key.name) === -1
).map((key) => {
promises.push(this.addColumn(key.name))
})
return Promise.all(promises)
}
createTable() {
return this.db.run(`CREATE TABLE IF NOT EXISTS "${this.name}" (id INTEGER PRIMARY KEY)`)
}
async getColumns() {
const result = await this.db.all('SELECT name FROM PRAGMA_TABLE_INFO(?)', [this.name])
const names = []
result.forEach(row => {
names.push(row.name)
})
return names
}
getIndexes() {
return this.db.all('select name FROM sqlite_master WHERE tbl_name = ? AND type="index"', [this.name]).then(indexes => {
return indexes.map((row) => {
return row.name
})
})
}
async addColumn(name) {
const query = [
'ALTER TABLE',
`"${this.name}"`,
'ADD COLUMN ',
`"${name}";`
].join(' ')
this.columns.push(name)
return this.db.run(query, []).then(() => {
this.cache.flush()
})
}
}
module.exports = Table