Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Views: 39537
1
###
2
Simple in memory client for testing synctable based functionality.
3
4
This simulates all of the database queries with a simple local database.
5
6
** This is only meant for testing running on the backend under node.js. **
7
8
**WARNING** this is not complete! I implemented enough of postgres-user-queries,
9
so that I could test the jupyter implementation. There are edge cases involving
10
queries, etc., which may just not work yet. It's a lot of work to just go
11
through and re-implement everything in postgres-user-queries...
12
13
Also, obviously, I didn't worry one speck about efficiency here -- this is just
14
for tiny local testing.
15
16
Part of CoCALC, which is (c) 2017, SageMath, Inc. and AGPLv3+ licensed.
17
###
18
19
async = require('async')
20
immutable = require('immutable')
21
22
misc = require('./misc')
23
{defaults, required} = misc
24
25
{SCHEMA, client_db} = require('smc-util/schema')
26
27
syncstring = require('./syncstring')
28
synctable = require('./synctable')
29
db_doc = require('./db-doc')
30
31
class exports.Client extends syncstring.TestBrowserClient1
32
constructor: (@_client_id=misc.uuid(), @_debounce_interval=0) ->
33
# @db is our personal in-memory "database"
34
# The keys are the database tables, and the values are
35
# the entries in the tables.
36
# Efficiency does not matter, of course -- this is 100% just
37
# for testing!
38
@account_id = misc.uuid()
39
@reset()
40
41
reset: =>
42
@removeAllListeners()
43
@_changefeeds = []
44
@db = immutable.Map()
45
46
sha1: (args...) =>
47
client_db.sha1(args...)
48
49
is_project: =>
50
return false
51
52
is_connected: =>
53
return true
54
55
is_signed_in: =>
56
return true
57
58
is_user: =>
59
return true
60
61
client_id: =>
62
return @_client_id
63
64
dbg: (f) =>
65
return (m...) ->
66
switch m.length
67
when 0
68
s = ''
69
when 1
70
s = m[0]
71
else
72
s = JSON.stringify(m)
73
console.log("#{(new Date()).toISOString()} - Client.#{f}: #{s}")
74
75
mark_file: =>
76
77
server_time: =>
78
return new Date()
79
80
_user_query_array: (opts) =>
81
if opts.changes and opts.query.length > 1
82
opts.cb("changefeeds only implemented for single table")
83
return
84
result = []
85
f = (query, cb) =>
86
@user_query
87
account_id : opts.account_id
88
project_id : opts.project_id
89
query : query
90
options : opts.options
91
cb : (err, x) =>
92
result.push(x); cb(err)
93
async.mapSeries(opts.query, f, (err) => opts.cb(err, result))
94
95
query: (opts) =>
96
opts = defaults opts,
97
query : required
98
changes : undefined
99
options : undefined # if given must be an array of objects, e.g., [{limit:5}]
100
timeout : undefined # ignored
101
cb : undefined
102
delete opts.timeout
103
@user_query(opts)
104
105
user_query: (opts) =>
106
opts = defaults opts,
107
account_id : @account_id
108
project_id : undefined
109
query : required
110
changes : undefined
111
options : []
112
cb : undefined
113
if misc.is_array(opts.query)
114
@_user_query_array(opts)
115
return
116
subs =
117
'{account_id}' : opts.account_id
118
'{project_id}' : opts.project_id
119
'{now}' : new Date()
120
if opts.changes?
121
changes =
122
id : misc.uuid()
123
cb : opts.cb
124
v = misc.keys(opts.query)
125
table = v[0]
126
query = opts.query[table]
127
if misc.is_array(query)
128
multi = true
129
query = query[0]
130
else
131
multi = false
132
@_user_query_functional_subs(query, SCHEMA[table]?.user_query.get?.fields)
133
is_set_query = undefined
134
if opts.options?
135
if not misc.is_array(opts.options)
136
opts.cb?("error")
137
return
138
for x in opts.options
139
if x.set?
140
is_set_query = !!x.set
141
options = (x for x in opts.options when not x.set?)
142
else
143
options = []
144
if misc.is_object(query)
145
query = misc.deep_copy(query)
146
misc.obj_key_subs(query, subs)
147
if not is_set_query?
148
is_set_query = not misc.has_null_leaf(query)
149
if is_set_query
150
# do a set query
151
if changes
152
opts.cb?("changefeeds only for read queries")
153
return
154
if not opts.account_id? and not opts.project_id?
155
opts.cb?("no anonymous set queries")
156
return
157
@user_set_query
158
account_id : opts.account_id
159
project_id : opts.project_id
160
table : table
161
query : query
162
options : opts.options
163
cb : (err, x) =>
164
opts.cb?(err, {query:{"#{table}":x}})
165
else
166
# do a get query
167
if changes and not multi
168
opts.cb?("changefeeds only implemented for multi-document queries")
169
return
170
@user_get_query
171
account_id : opts.account_id
172
project_id : opts.project_id
173
table : table
174
query : query
175
options : options
176
multi : multi
177
changes : changes
178
cb : (err, x) =>
179
opts.cb?(err, if not err then {query:{"#{table}":x}})
180
else
181
opts.cb?("invalid user_query of '#{table}' -- query must be an object")
182
183
_user_query_functional_subs: (query, fields) =>
184
if fields?
185
for field, val of fields
186
if typeof(val) == 'function'
187
query[field] = val(query, @)
188
189
user_get_query: (opts) =>
190
opts = defaults opts,
191
account_id : undefined
192
project_id : undefined
193
table : required
194
query : required
195
multi : required
196
options : required
197
changes : undefined
198
cb : required
199
client_query = SCHEMA[opts.table]?.user_query
200
schema = SCHEMA[opts.table]
201
table_name = SCHEMA[opts.table].virtual ? opts.table
202
table = @db.get(table_name)
203
204
if opts.multi
205
# list of all matches
206
result = []
207
table?.forEach (x) ->
208
y = x.toJS()
209
if matches_query(y, opts.query)
210
result.push(y)
211
opts.cb(undefined, result)
212
if opts.changes
213
# setup changefeed
214
@_changefeeds.push
215
id : opts.changes.id
216
cb : opts.changes.cb
217
table : table_name
218
query : opts.query
219
else
220
# one match
221
key = to_key(misc.copy_with(misc.copy(opts.query), schema.primary_key))
222
obj = table?.get(key)?.toJS()
223
if obj?
224
obj = misc.copy_with(obj, misc.keys(opts.query))
225
opts.cb(undefined, obj)
226
return
227
228
user_set_query: (opts) =>
229
opts = defaults opts,
230
account_id : undefined
231
project_id : undefined
232
table : required
233
query : required
234
options : undefined
235
cb : required
236
schema = SCHEMA[opts.table]
237
table_name = schema.virtual ? opts.table
238
table = @db.get(table_name)
239
if not table?
240
table = immutable.Map()
241
@db = @db.set(table_name, table)
242
key = to_key(misc.copy_with(misc.copy(opts.query), schema.primary_key))
243
cur = table.get(key)
244
query = immutable.fromJS(opts.query)
245
if cur?
246
new_val = cur.merge(query)
247
table = table.set(key, new_val)
248
obj = new_val.toJS()
249
for c in @_changefeeds
250
if c.table == table_name and matches_query(obj, c.query)
251
c.cb(undefined, {old_val:cur.toJS(), new_val:obj})
252
else
253
table = table.set(key, query)
254
for c in @_changefeeds
255
if c.table == table_name and matches_query(opts.query, c.query)
256
c.cb(undefined, {new_val:opts.query})
257
258
@db = @db.set(table_name, table)
259
opts.cb(undefined, opts.query)
260
return
261
262
query_cancel: =>
263
264
sync_table: (query, options, debounce_interval=0) =>
265
debounce_interval = @_debounce_interval # hard coded for testing
266
return synctable.sync_table(query, options, @, debounce_interval, 0, false)
267
268
sync_string: (opts) =>
269
opts = defaults opts,
270
id : undefined
271
project_id : undefined
272
path : undefined
273
file_use_interval : 'default'
274
cursors : false
275
save_interval : 0
276
opts.client = @
277
return new syncstring.SyncString(opts)
278
279
sync_db: (opts) =>
280
opts = defaults opts,
281
project_id : required
282
path : required
283
primary_keys : required
284
string_cols : undefined
285
cursors : false
286
change_throttle : 0
287
save_interval : 0
288
opts.client = @
289
return new db_doc.SyncDB(opts)
290
291
292
293
294
295
# Well-defined JSON.stringify...
296
json_stable = require('json-stable-stringify')
297
to_key = (s) ->
298
if immutable.Map.isMap(s)
299
s = s.toJS()
300
return json_stable(s)
301
302
303
matches_query = (obj, query) ->
304
for k, v of query
305
if v? and obj[k] != v
306
return false
307
return true
308