Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
| Download
Views: 39598
1
###############################################################################
2
#
3
# CoCalc: Collaborative Calculation in the Cloud
4
#
5
# Copyright (C) 2016, Sagemath Inc.
6
#
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
#
20
###############################################################################
21
22
async = require('async')
23
sqlite3 = require('sqlite3') # from https://github.com/mapbox/node-sqlite3
24
winston = require('winston')
25
26
misc = require('smc-util/misc')
27
{defaults, required} = misc
28
29
# Set the log level
30
if !!process.env.SMC_DEBUG
31
winston.remove(winston.transports.Console)
32
winston.add(winston.transports.Console, {level: 'debug', timestamp:true, colorize:true})
33
34
35
36
37
#########################################################################
38
#
39
# Interface to a SQlite Database.
40
41
#
42
# x={};require('sqlite').sqlite(filename:'/tmp/test.db',cb:(e,d)->console.log('done',e);x.d=d)
43
#
44
# cached and avoiding race conditions (should refactor this pattern!)
45
sqlite_cache = {}
46
sqlite_cache_cb = {}
47
exports.sqlite = (opts) ->
48
opts = defaults opts,
49
filename : required
50
cb : required
51
if sqlite_cache[opts.filename]?
52
opts.cb(undefined, sqlite_cache[opts.filename])
53
return
54
v = sqlite_cache_cb[opts.filename]
55
if v?
56
v.push(opts.cb)
57
return
58
else
59
v = sqlite_cache_cb[opts.filename] = [opts.cb]
60
new SQLite opts.filename, (err, db) ->
61
delete sqlite_cache_cb[opts.filename]
62
if not err
63
sqlite_cache[opts.filename] = db
64
for f in v
65
f(err, db)
66
67
class SQLite
68
constructor: (@filename, cb) ->
69
@db = new sqlite3.Database @filename, (err) =>
70
if err
71
cb(err)
72
else
73
cb(undefined, @)
74
75
###
76
77
x={};require('sqlite').sqlite(filename:'/tmp/test.db',cb:(e,d)->console.log('done',e);x.d=d;x.d.sql(query:'select * from projects',cb:console.log))
78
79
###
80
sql: (opts) =>
81
opts = defaults opts,
82
query : required
83
vals : []
84
cb : undefined
85
winston.debug("sql: query='#{opts.query}', vals=#{misc.to_json(opts.vals)}")
86
@db.prepare(opts.query, opts.vals).all (err, rows) =>
87
opts.cb?(err, rows)
88
89
_format: (x, cond) =>
90
if not cond or misc.len(cond) == 0
91
return {query:"", vals:[]}
92
q = []
93
vals = []
94
for k, v of cond
95
q.push("#{k}=?")
96
vals.push(v)
97
return {query:" #{x} #{q.join(',')}", vals:vals}
98
99
_where: (cond) => @_format('WHERE', cond)
100
_set: (cond) => @_format('SET', cond)
101
102
# x={};require('sqlite').sqlite(filename:'/tmp/test.db',cb:(e,d)->console.log('done',e);x.d=d;x.d.count(table:'projects',cb:console.log))
103
# x={};require('sqlite').sqlite(filename:'/tmp/test.db',cb:(e,d)->console.log('done',e);x.d=d;x.d.count(table:'projects',where:{project_id:'a418a066-6e44-464b-a0fe-934679b0cf97'},cb:console.log))
104
count: (opts) =>
105
opts = defaults opts,
106
table : required
107
where : undefined
108
cb : required
109
if opts.where?
110
w = @_where(opts.where)
111
query = "SELECT count(*) FROM #{opts.table} #{w.query}"
112
vals = w.vals
113
else
114
query = "SELECT count(*) FROM #{opts.table}"
115
vals = []
116
117
@sql
118
query : query
119
vals : vals
120
cb : (err, result) =>
121
if err
122
opts.cb(err)
123
else
124
opts.cb(undefined, result[0]['count(*)'])
125
126
update: (opts) =>
127
opts = defaults opts,
128
table : required
129
where : required
130
set : {}
131
cb : undefined
132
count = undefined
133
async.series([
134
(cb) =>
135
@count
136
table : opts.table
137
where : opts.where
138
cb : (err, n) =>
139
count = n; cb(err)
140
(cb) =>
141
insert = () =>
142
w = @_where(opts.where)
143
s = @_set(opts.set)
144
@sql
145
query : "UPDATE #{opts.table} #{s.query} #{w.query}"
146
vals : s.vals.concat(w.vals)
147
cb : cb
148
if count > 0
149
insert()
150
else
151
columns = []
152
vals = []
153
vals_holder = []
154
for x in [opts.set, opts.where]
155
for k, v of x
156
columns.push("\"#{k}\"")
157
vals.push(v)
158
vals_holder.push('?')
159
@sql
160
query : "INSERT INTO #{opts.table} (#{columns.join(',')}) VALUES (#{vals_holder.join(',')})"
161
vals : vals
162
cb : (err) =>
163
if err
164
# We still have to try this and if it fails -- do to something else
165
# doing an insert after the count above, then do an update instead.
166
insert()
167
else
168
cb(err)
169
], (err) => opts.cb?(err))
170
171
delete: (opts={}) ->
172
opts = defaults opts,
173
table : undefined
174
where : {}
175
cb : undefined
176
w = @_where(opts.where)
177
@sql
178
query : "DELETE FROM #{opts.table} #{w.query}"
179
vals : w.vals
180
cb : opts.cb
181
182
select: (opts={}) =>
183
opts = defaults opts,
184
table : required # string -- the table to query
185
columns : undefined # list -- columns to extract
186
where : undefined # object -- conditions to impose; undefined = return everything
187
cb : required # callback(error, results)
188
w = @_where(opts.where)
189
if opts.columns?
190
columns = opts.columns.join(',')
191
else
192
columns = '*'
193
@sql
194
query : "SELECT #{columns} FROM #{opts.table} #{w.query}"
195
vals : w.vals
196
cb : opts.cb
197
198