yax-fauna 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1104 @@
1
+ RSpec.describe Fauna::Query do
2
+ before(:all) do
3
+ create_test_db
4
+ @test_class = client.query { create ref('classes'), name: 'query_test' }[:ref]
5
+
6
+ index_x = client.query do
7
+ create ref('indexes'), name: 'query_by_x', source: @test_class, terms: [{ field: %w(data x) }]
8
+ end
9
+ index_y = client.query do
10
+ create ref('indexes'), name: 'query_by_y', source: @test_class, terms: [{ field: %w(data y) }]
11
+ end
12
+
13
+ wait_for_index(index_x[:ref], index_y[:ref])
14
+
15
+ @test_by_x = index_x[:ref]
16
+ @test_by_y = index_y[:ref]
17
+ end
18
+
19
+ after(:all) do
20
+ destroy_test_db
21
+ end
22
+
23
+ # Alias for creating test class instance with provided data
24
+ def create_instance(data = {})
25
+ client.query { create(@test_class, data: data) }
26
+ end
27
+
28
+ # Helper to collect all the contents of a set
29
+ def get_set_data(set, params = {})
30
+ data = []
31
+
32
+ page = client.query { paginate(set, params) }
33
+ data += page[:data]
34
+ while page.key? :after
35
+ page = client.query { paginate(set, params.merge(after: page[:after])) }
36
+ data += page[:data]
37
+ end
38
+
39
+ data
40
+ end
41
+
42
+ describe Fauna::Query::Expr do
43
+ describe '#to_s' do
44
+ it 'converts to string' do
45
+ expr = Fauna::Query::Expr.new(
46
+ add: Fauna::Query::Expr.new(
47
+ [1, Fauna::Query::Expr.new(divide: Fauna::Query::Expr.new([4, 2]))]
48
+ )
49
+ )
50
+ as_string = 'Expr({:add=>Expr([1, Expr({:divide=>Expr([4, 2])})])})'
51
+
52
+ expect(expr.to_s).to eq(as_string)
53
+ end
54
+ end
55
+
56
+ describe '#==' do
57
+ it 'equals identical expression' do
58
+ expr1 = Fauna::Query::Expr.new(add: Fauna::Query::Expr.new([1, 2]))
59
+ expr2 = Fauna::Query::Expr.new(add: Fauna::Query::Expr.new([1, 2]))
60
+
61
+ expect(expr1).to eq(expr2)
62
+ end
63
+
64
+ it 'does not equal different expression' do
65
+ expr1 = Fauna::Query::Expr.new(add: Fauna::Query::Expr.new([1, 2]))
66
+ expr2 = Fauna::Query::Expr.new(
67
+ add: Fauna::Query::Expr.new(
68
+ [1, Fauna::Query::Expr.new(divide: Fauna::Query::Expr.new([4, 2]))]
69
+ )
70
+ )
71
+
72
+ expect(expr1).not_to eq(expr2)
73
+ end
74
+ end
75
+ end
76
+
77
+ describe '#expr' do
78
+ it 'maintains lexical scope' do
79
+ def test_method
80
+ 'foo'
81
+ end
82
+ test_var = 'bar'
83
+
84
+ expect(Fauna::Query.expr { test_method }).to eq('foo')
85
+ expect(Fauna::Query.expr { test_var }).to eq('bar')
86
+ end
87
+
88
+ it 'converts proc blocks' do
89
+ expr = Fauna::Query.expr { proc { |a| add(a, a) } }
90
+ query = { lambda: :a, expr: { add: [{ var: :a }, { var: :a }] } }
91
+
92
+ expect(to_json(expr)).to eq(to_json(query))
93
+ end
94
+
95
+ it 'recursively wraps arrays' do
96
+ expr = Fauna::Query.expr { [1, { foo: 2 }, add(1, 2)] }
97
+ query = [1, { object: { foo: 2 } }, { add: [1, 2] }]
98
+
99
+ expect(to_json(expr)).to eq(to_json(query))
100
+ end
101
+
102
+ it 'recursively wraps hashes' do
103
+ expr = Fauna::Query.expr { { x: 1, y: { foo: 2 }, z: add(1, 2), a: [1, 2, { b: 3, c: add(1, 3) }] } }
104
+ query = { object: { x: 1, y: { object: { foo: 2 } }, z: { add: [1, 2] }, a: [1, 2, { object: { b: 3, c: { add: [1, 3] } } }] } }
105
+
106
+ expect(to_json(expr)).to eq(to_json(query))
107
+ end
108
+
109
+ it 'recursively wraps to_h objects' do
110
+ class ToH
111
+ def initialize(obj)
112
+ @obj = obj
113
+ end
114
+
115
+ def to_h
116
+ @obj
117
+ end
118
+ end
119
+
120
+ expr = Fauna::Query.expr { ToH.new(x: 1, y: ToH.new(foo: 2), z: add(1, 2)) }
121
+ query = { object: { x: 1, y: { object: { foo: 2 } }, z: { add: [1, 2] } } }
122
+
123
+ expect(to_json(expr)).to eq(to_json(query))
124
+ end
125
+
126
+ it 'recursively wraps to_hash objects' do
127
+ class ToHash
128
+ def initialize(obj)
129
+ @obj = obj
130
+ end
131
+
132
+ def to_hash
133
+ @obj
134
+ end
135
+ end
136
+
137
+ expr = Fauna::Query.expr { ToHash.new(x: 1, y: ToHash.new(foo: 2), z: add(1, 2)) }
138
+ query = { object: { x: 1, y: { object: { foo: 2 } }, z: { add: [1, 2] } } }
139
+
140
+ expect(to_json(expr)).to eq(to_json(query))
141
+ end
142
+
143
+ it 'recursively wraps special types' do
144
+ expr = Fauna::Query.expr { { x: { y: Time.at(0).utc } } }
145
+ query = { object: { x: { object: { y: { :@ts => '1970-01-01T00:00:00.000000000Z' } } } } }
146
+
147
+ expect(to_json(expr)).to eq(to_json(query))
148
+ end
149
+
150
+ it 'round-trips special types', skip: 'Support for auto-escaping of special types is deferred' do
151
+ expect(client.query { { '@ref' => 'foo' } }).to eq(:@ref => 'foo')
152
+ end
153
+
154
+ it 'fails on unserializable objects' do
155
+ obj = DummyClass.new(random_string)
156
+
157
+ expect { Fauna::Query.expr { obj } }.to raise_error(Fauna::SerializationError)
158
+ end
159
+ end
160
+
161
+ describe '#ref' do
162
+ it 'returns a ref from a string' do
163
+ cls = random_string
164
+ id = random_number.to_s
165
+ str = "classes/#{cls}/#{id}"
166
+ expect(client.query { ref(str) }).to eq(Fauna::Ref.new(id, Fauna::Ref.new(cls, Fauna::Native.classes)))
167
+ end
168
+
169
+ it 'constructs a ref' do
170
+ expect(client.query { ref(@test_class, '123') }).to eq(Fauna::Ref.new('123', @test_class))
171
+ expect(client.query { ref(@test_class, new_id) }.id).to match(%r{\d+$})
172
+ end
173
+ end
174
+
175
+ describe '#abort' do
176
+ it 'aborts the execution' do
177
+ expect { client.query { abort('message') }}.to raise_error(Fauna::BadRequest)
178
+ end
179
+ end
180
+
181
+ describe '#object' do
182
+ it 'wraps fields in object' do
183
+ data = { a: random_string, b: random_number }
184
+ expect(Fauna::Query.object(data).raw).to eq(object: data)
185
+ end
186
+ end
187
+
188
+ describe '#query' do
189
+ it 'wraps fields in query' do
190
+ data = Fauna::Query::Expr.new(lambda: random_string, expr: Fauna::Query::Expr.new(add: Fauna::Query::Expr.wrap([1, 1])))
191
+ expect(Fauna::Query.query(data).raw).to eq(query: data)
192
+ end
193
+ end
194
+
195
+ describe '#at' do
196
+ it 'performs at for given expression' do
197
+ instance = create_instance
198
+ ref = instance[:ref]
199
+ ts = instance[:ts]
200
+
201
+ prev_ts = ts - 1
202
+ value = random_number
203
+ client.query { insert(ref, prev_ts, :create, data: { x: value }) }
204
+
205
+ expect(client.query { at(prev_ts, get(ref)) }[:data]).to eq(x: value)
206
+ end
207
+ end
208
+
209
+ describe '#let' do
210
+ it 'performs let with expression' do
211
+ x = random_number
212
+ expect(client.query { let({ x: x }, var(:x)) }).to eq(x)
213
+ end
214
+
215
+ it 'performs let with block' do
216
+ x = random_number
217
+ expect(client.query { let(x: x) { x } }).to eq(x)
218
+ end
219
+ end
220
+
221
+ describe '#var' do
222
+ it 'creates a var' do
223
+ name = random_string
224
+ expect(Fauna::Query.var(name).raw).to eq(var: name)
225
+ end
226
+ end
227
+
228
+ describe '#if_' do
229
+ it 'performs an if' do
230
+ expect(client.query { if_(true, 't', 'f') }).to eq('t')
231
+ expect(client.query { if_(false, 't', 'f') }).to eq('f')
232
+ end
233
+ end
234
+
235
+ describe '#do_' do
236
+ it 'performs do' do
237
+ instance = create_instance
238
+ expect(client.query { do_(delete(instance[:ref]), 1) }).to eq(1)
239
+ expect(client.query { exists instance[:ref] }).to be(false)
240
+ end
241
+ end
242
+
243
+ describe '#lambda' do
244
+ it 'raises when block takes no arguments' do
245
+ expect { Fauna::Query.lambda {} }.to raise_error(ArgumentError)
246
+ end
247
+
248
+ it 'raises when block takes splat argument' do
249
+ expect { Fauna::Query.lambda { |*vars| add(vars) } }.to raise_error(ArgumentError)
250
+ end
251
+
252
+ it 'performs lambda from single argument' do
253
+ expr = Fauna::Query.expr { lambda { |a| add(a, a) } }
254
+ query = { lambda: :a, expr: { add: [{ var: :a }, { var: :a }] } }
255
+
256
+ expect(to_json(expr)).to eq(to_json(query))
257
+ expect(client.query { map([1, 2, 3], expr) }).to eq([2, 4, 6])
258
+ end
259
+
260
+ it 'performs lambda from multiple arguments' do
261
+ expr = Fauna::Query.expr { lambda { |a, b| [b, a] } }
262
+ query = { lambda: [:a, :b], expr: [{ var: :b }, { var: :a }] }
263
+
264
+ expect(to_json(expr)).to eq(to_json(query))
265
+ expect(client.query { map([[1, 2], [3, 4]], expr) }).to eq([[2, 1], [4, 3]])
266
+ end
267
+ end
268
+
269
+ describe '#lambda_expr' do
270
+ it 'performs lambda from expression' do
271
+ expr = Fauna::Query.expr { lambda_expr(:a, add(var(:a), var(:a))) }
272
+ query = { lambda: :a, expr: { add: [{ var: :a }, { var: :a }] } }
273
+
274
+ expect(to_json(expr)).to eq(to_json(query))
275
+ expect(client.query { map([1, 2, 3], expr) }).to eq([2, 4, 6])
276
+ end
277
+
278
+ it 'destructures single element arrays' do
279
+ expr = Fauna::Query.expr { lambda_expr([:a], add(var(:a), var(:a))) }
280
+ query = { lambda: [:a], expr: { add: [{ var: :a }, { var: :a }] } }
281
+
282
+ expect(to_json(expr)).to eq(to_json(query))
283
+ expect(client.query { map([[1], [2], [3]], expr) }).to eq([2, 4, 6])
284
+ end
285
+ end
286
+
287
+ describe '#call' do
288
+ it 'performs called function' do
289
+ test_func = client.query do
290
+ func_body = lambda do |x|
291
+ [add(x, 1), add(x, 2), add(x, 3)]
292
+ end
293
+
294
+ create ref('functions'), name: 'call_func_test', body: query(func_body)
295
+ end
296
+
297
+ x = random_number
298
+
299
+ expect(client.query { call(test_func[:ref], x) }).to eq([x + 1, x + 2, x + 3])
300
+ end
301
+
302
+ it 'handles multiple arguments' do
303
+ test_func = client.query do
304
+ func_body = lambda do |x, y, z|
305
+ [multiply(x, 2), multiply(y, 3), multiply(z, 4)]
306
+ end
307
+
308
+ create ref('functions'), name: 'call_multiarg_test', body: query(func_body)
309
+ end
310
+
311
+ x = random_number
312
+ y = random_number
313
+ z = random_number
314
+
315
+ expect(client.query { call(test_func[:ref], x, y, z) }).to eq([x * 2, y * 3, z * 4])
316
+ end
317
+ end
318
+
319
+ describe '#map' do
320
+ it 'performs map from expression' do
321
+ input = (1..3).collect { random_number }
322
+ output = input.collect { |x| 2 * x }
323
+
324
+ expect(client.query { map(input, lambda { |a| multiply 2, a }) }).to eq(output)
325
+ end
326
+
327
+ it 'performs map from block' do
328
+ input = (1..3).collect { random_number }
329
+ output = input.collect { |x| 2 * x }
330
+
331
+ expect(client.query { map(input) { |a| multiply 2, a } }).to eq(output)
332
+ end
333
+ end
334
+
335
+ describe '#foreach' do
336
+ before(:each) do
337
+ @refs = (1..3).collect { create_instance[:ref] }
338
+
339
+ # Sanity check
340
+ expect(client.query { @refs.collect { |ref| exists ref } }).to eq(@refs.collect { true })
341
+ end
342
+
343
+ it 'performs foreach from expression' do
344
+ client.query { foreach @refs, lambda { |a| delete a } }
345
+
346
+ expect(client.query { @refs.collect { |ref| exists ref } }).to eq(@refs.collect { false })
347
+ end
348
+
349
+ it 'performs foreach from block' do
350
+ client.query { foreach(@refs) { |a| delete a } }
351
+
352
+ expect(client.query { @refs.collect { |ref| exists ref } }).to eq(@refs.collect { false })
353
+ end
354
+ end
355
+
356
+ describe '#filter' do
357
+ it 'performs filter from expression' do
358
+ expect(client.query { filter([1, 2, 3, 4], lambda { |a| equals modulo(a, 2), 0 }) }).to eq([2, 4])
359
+ end
360
+
361
+ it 'performs filter from block' do
362
+ expect(client.query { filter([1, 2, 3, 4]) { |a| equals modulo(a, 2), 0 } }).to eq([2, 4])
363
+ end
364
+ end
365
+
366
+ describe '#take' do
367
+ it 'performs take' do
368
+ expect(client.query { take(1, [1, 2]) }).to eq([1])
369
+ expect(client.query { take(3, [1, 2]) }).to eq([1, 2])
370
+ expect(client.query { take(-1, [1, 2]) }).to eq([])
371
+ end
372
+ end
373
+
374
+ describe '#drop' do
375
+ it 'performs drop' do
376
+ expect(client.query { drop(1, [1, 2]) }).to eq([2])
377
+ expect(client.query { drop(3, [1, 2]) }).to eq([])
378
+ expect(client.query { drop(-1, [1, 2]) }).to eq([1, 2])
379
+ end
380
+ end
381
+
382
+ describe '#prepend' do
383
+ it 'performs prepend' do
384
+ expect(client.query { prepend([1, 2, 3], [4, 5, 6]) }).to eq([1, 2, 3, 4, 5, 6])
385
+ end
386
+ end
387
+
388
+ describe '#append' do
389
+ it 'performs append' do
390
+ expect(client.query { append([1, 2, 3], [4, 5, 6]) }).to eq([4, 5, 6, 1, 2, 3])
391
+ end
392
+ end
393
+
394
+ describe '#is_empty' do
395
+ it 'performs is_empty on collections' do
396
+ expect(client.query { is_empty([]) }).to be(true)
397
+ expect(client.query { is_empty([1, 2, 3]) }).to be(false)
398
+ end
399
+
400
+ it 'performs is_empty on pages' do
401
+ expect(client.query { is_empty(paginate(classes(), size: 0)) }).to be(true)
402
+ expect(client.query { is_empty(paginate(classes(), size: 1)) }).to be(false)
403
+ end
404
+ end
405
+
406
+ describe '#is_nonempty' do
407
+ it 'performs is_nonempty on collections' do
408
+ expect(client.query { is_nonempty([]) }).to be(false)
409
+ expect(client.query { is_nonempty([1, 2, 3]) }).to be(true)
410
+ end
411
+
412
+ it 'performs is_empty on pages' do
413
+ expect(client.query { is_nonempty(paginate(classes(), size: 0)) }).to be(false)
414
+ expect(client.query { is_nonempty(paginate(classes(), size: 1)) }).to be(true)
415
+ end
416
+ end
417
+
418
+ describe '#get' do
419
+ it 'performs get' do
420
+ instance = create_instance
421
+
422
+ expect(client.query { get instance[:ref] }).to eq(instance)
423
+ end
424
+ end
425
+
426
+ describe '#key_from_secret' do
427
+ it 'gets key from secret' do
428
+ # Create a key
429
+ db_ref = admin_client.query { create(ref('databases'), name: random_string) }[:ref]
430
+ key = admin_client.query { create_key(database: db_ref, role: 'server') }
431
+
432
+ expect(admin_client.query { key_from_secret key[:secret] }).to eq(admin_client.query { get key[:ref] })
433
+ end
434
+ end
435
+
436
+ describe '#paginate' do
437
+ before do
438
+ @x_value = random_number
439
+ @x_refs = (1..3).collect { create_instance(x: @x_value)[:ref] }
440
+ end
441
+
442
+ it 'performs paginate' do
443
+ set = Fauna::Query.match(@test_by_x, @x_value)
444
+
445
+ expect(get_set_data(set, size: 1)).to eq(@x_refs)
446
+ end
447
+
448
+ it 'performs paginate with sources' do
449
+ response = {
450
+ data: @x_refs.collect do |ref|
451
+ { sources: [Fauna::SetRef.new(match: @test_by_x, terms: @x_value)], value: ref }
452
+ end
453
+ }
454
+
455
+ expect(client.query { paginate(match(@test_by_x, @x_value), sources: true) }).to eq(response)
456
+ end
457
+ end
458
+
459
+ describe '#exists' do
460
+ it 'performs exists' do
461
+ ref = create_instance[:ref]
462
+
463
+ expect(client.query { exists ref }).to be(true)
464
+ client.query { delete ref }
465
+ expect(client.query { exists ref }).to be(false)
466
+
467
+ # Sanity check
468
+ expect { client.query { get ref } }.to raise_error(Fauna::NotFound)
469
+ end
470
+ end
471
+
472
+ describe '#create' do
473
+ it 'performs create' do
474
+ instance = client.query { create(@test_class, {}) }
475
+
476
+ expect(instance[:ref].class_).to eq(@test_class)
477
+ expect(client.query { exists instance[:ref] }).to be(true)
478
+ end
479
+ end
480
+
481
+ describe '#update' do
482
+ it 'performs update' do
483
+ x = random_number
484
+ y = random_number
485
+ ref = create_instance(x: x)[:ref]
486
+
487
+ instance = client.query { update(ref, data: { y: y }) }
488
+ expect(instance[:data]).to eq(x: x, y: y)
489
+ end
490
+ end
491
+
492
+ describe '#replace' do
493
+ it 'performs replace' do
494
+ x = random_number
495
+ y = random_number
496
+ ref = create_instance(x: x)[:ref]
497
+
498
+ instance = client.query { replace(ref, data: { y: y }) }
499
+ expect(instance[:data]).to eq(y: y)
500
+ end
501
+ end
502
+
503
+ describe '#delete' do
504
+ it 'performs delete' do
505
+ ref = create_instance[:ref]
506
+
507
+ client.query { delete ref }
508
+ expect(client.query { exists ref }).to be(false)
509
+ end
510
+ end
511
+
512
+ describe '#insert' do
513
+ it 'performs insert' do
514
+ instance = create_instance
515
+ ref = instance[:ref]
516
+ ts = instance[:ts]
517
+
518
+ prev_ts = ts - 1
519
+ value = random_number
520
+ client.query { insert(ref, prev_ts, :create, data: { x: value }) }
521
+
522
+ expect(client.query { get(ref, ts: prev_ts) }[:data]).to eq(x: value)
523
+ end
524
+ end
525
+
526
+ describe '#remove' do
527
+ it 'performs remove' do
528
+ # Create the instance
529
+ instance = create_instance
530
+ ref = instance[:ref]
531
+
532
+ # Change the instance
533
+ new_instance = client.query { replace(ref, data: { x: random_number }) }
534
+ expect(client.query { get(ref) }).to eq(new_instance)
535
+
536
+ # Delete the event
537
+ client.query { remove(ref, new_instance[:ts], :create) }
538
+
539
+ # Assert it changed
540
+ expect(client.query { get(ref) }).to eq(instance)
541
+ end
542
+ end
543
+
544
+ describe '#create_class' do
545
+ it 'creates a class' do
546
+ # Create a class
547
+ ref = client.query { create_class(name: random_string) }[:ref]
548
+
549
+ # Assert it was created
550
+ expect(client.query { exists(ref) }).to be(true)
551
+ end
552
+ end
553
+
554
+ describe '#create_index' do
555
+ it 'creates an index' do
556
+ # Create an index
557
+ class_ref = client.query { create(ref('classes'), name: random_string) }[:ref]
558
+ ref = client.query { create_index(name: random_string, source: class_ref) }[:ref]
559
+
560
+ # Assert it was created
561
+ expect(client.query { exists(ref) }).to be(true)
562
+ end
563
+ end
564
+
565
+ describe '#create_database' do
566
+ it 'creates a database' do
567
+ # Create a database
568
+ ref = admin_client.query { create_database(name: random_string) }[:ref]
569
+
570
+ # Assert it was created
571
+ expect(admin_client.query { exists(ref) }).to be(true)
572
+ end
573
+ end
574
+
575
+ describe '#create_key' do
576
+ it 'creates a key' do
577
+ # Create a key
578
+ db_ref = admin_client.query { create(ref('databases'), name: random_string) }[:ref]
579
+ ref = admin_client.query { create_key(database: db_ref, role: 'server') }[:ref]
580
+
581
+ # Assert it was created
582
+ expect(admin_client.query { exists(ref) }).to be(true)
583
+ end
584
+ end
585
+
586
+ describe '#create_function' do
587
+ it 'creates a function' do
588
+ # Create a function
589
+ ref = client.query { create_function(name: random_string, body: query(lambda { |a| add(a, 1) })) }[:ref]
590
+
591
+ # Assert it was created
592
+ expect(client.query { exists(ref) }).to be(true)
593
+ end
594
+ end
595
+
596
+ describe 'sets' do
597
+ before do
598
+ @x_value = random_number
599
+ @y_value = random_number
600
+
601
+ @ref_x = create_instance(x: @x_value)[:ref]
602
+ @ref_y = create_instance(y: @y_value)[:ref]
603
+ @ref_xy = create_instance(x: @x_value, y: @y_value)[:ref]
604
+ end
605
+
606
+ describe '#events' do
607
+ it 'performs events' do
608
+ client.query { update @ref_x, data: {x: random_number} }
609
+ client.query { delete @ref_x }
610
+
611
+ events = client.query { paginate events(@ref_x) }[:data]
612
+
613
+ expect(events.count).to be(3)
614
+
615
+ expect(events[0][:action]).to eq('create')
616
+ expect(events[0][:instance]).to eq(@ref_x)
617
+
618
+ expect(events[1][:action]).to eq('update')
619
+ expect(events[1][:instance]).to eq(@ref_x)
620
+
621
+ expect(events[2][:action]).to eq('delete')
622
+ expect(events[2][:instance]).to eq(@ref_x)
623
+ end
624
+ end
625
+
626
+ describe '#singleton' do
627
+ it 'performs singleton' do
628
+ client.query { update @ref_x, data: {x: random_number} }
629
+ client.query { delete @ref_x }
630
+
631
+ events = client.query { paginate events(singleton(@ref_x)) }[:data]
632
+
633
+ expect(events.count).to be(2)
634
+
635
+ expect(events[0][:action]).to eq('add')
636
+ expect(events[0][:instance]).to eq(@ref_x)
637
+
638
+ expect(events[1][:action]).to eq('remove')
639
+ expect(events[1][:instance]).to eq(@ref_x)
640
+ end
641
+ end
642
+
643
+ describe '#match' do
644
+ it 'performs match' do
645
+ set = Fauna::Query.expr { match(@test_by_x, @x_value) }
646
+ expect(get_set_data(set)).to contain_exactly(@ref_x, @ref_xy)
647
+ end
648
+ end
649
+
650
+ describe '#union' do
651
+ it 'performs union' do
652
+ set = Fauna::Query.expr { union(match(@test_by_x, @x_value), match(@test_by_y, @y_value)) }
653
+ expect(get_set_data(set)).to contain_exactly(@ref_x, @ref_y, @ref_xy)
654
+ end
655
+ end
656
+
657
+ describe '#intersection' do
658
+ it 'performs intersection' do
659
+ set = Fauna::Query.expr { intersection(match(@test_by_x, @x_value), match(@test_by_y, @y_value)) }
660
+ expect(get_set_data(set)).to contain_exactly(@ref_xy)
661
+ end
662
+ end
663
+
664
+ describe '#difference' do
665
+ it 'performs difference' do
666
+ set = Fauna::Query.expr { difference(match(@test_by_x, @x_value), match(@test_by_y, @y_value)) }
667
+ expect(get_set_data(set)).to contain_exactly(@ref_x)
668
+ end
669
+ end
670
+ end
671
+
672
+ describe '#distinct' do
673
+ before do
674
+ over_z = client.query do
675
+ create ref('indexes'), name: 'query_over_z', source: @test_class, values: [{ field: %w(data z) }]
676
+ end
677
+ wait_for_index(over_z[:ref])
678
+ @test_over_z = over_z[:ref]
679
+
680
+ @refs = []
681
+ @refs << client.query { create @test_class, data: { z: 0 } }[:ref]
682
+ @refs << client.query { create @test_class, data: { z: 1 } }[:ref]
683
+ @refs << client.query { create @test_class, data: { z: 1 } }[:ref]
684
+ end
685
+
686
+ it 'performs distinct' do
687
+ set = Fauna::Query.match(@test_over_z)
688
+ distinct = Fauna::Query.distinct(set)
689
+
690
+ expect(get_set_data(set)).to eq([0, 1, 1])
691
+ expect(get_set_data(distinct)).to eq([0, 1])
692
+ end
693
+ end
694
+
695
+ describe '#join' do
696
+ before do
697
+ @x_value = random_number
698
+ @join_refs = (1..3).collect { create_instance(x: @x_value)[:ref] }
699
+ @assoc_refs = @join_refs.collect { |ref| create_instance(y: ref)[:ref] }
700
+ end
701
+
702
+ context 'with expression' do
703
+ it 'performs join' do
704
+ source = Fauna::Query.match(@test_by_x, @x_value)
705
+ expect(get_set_data(source)).to eq(@join_refs)
706
+
707
+ # Get associated refs
708
+ set = Fauna::Query.expr { join(source, lambda { |a| match(@test_by_y, a) }) }
709
+ expect(get_set_data(set)).to eq(@assoc_refs)
710
+ end
711
+ end
712
+
713
+ context 'with block' do
714
+ it 'performs join' do
715
+ source = Fauna::Query.match(@test_by_x, @x_value)
716
+ expect(get_set_data(source)).to eq(@join_refs)
717
+
718
+ # Get associated refs
719
+ set = Fauna::Query.expr { join(source) { |a| match(@test_by_y, a) } }
720
+ expect(get_set_data(set)).to eq(@assoc_refs)
721
+ end
722
+ end
723
+
724
+ context 'with index' do
725
+ it 'performs join' do
726
+ source = Fauna::Query.match(@test_by_x, @x_value)
727
+ expect(get_set_data(source)).to eq(@join_refs)
728
+
729
+ # Get associated refs
730
+ set = Fauna::Query.expr { join(source, @test_by_y) }
731
+ expect(get_set_data(set)).to eq(@assoc_refs)
732
+ end
733
+ end
734
+ end
735
+
736
+ describe 'authentication' do
737
+ before do
738
+ @password = random_string
739
+ @user = client.query { create @test_class, credentials: { password: @password } }
740
+ end
741
+
742
+ describe '#login' do
743
+ it 'performs login' do
744
+ token = client.query { login @user[:ref], password: @password }
745
+ user_client = get_client secret: token[:secret]
746
+
747
+ self_ = Fauna::Ref.new('self', Fauna::Native.tokens)
748
+ expect(user_client.query { select(:ref, get(self_)) }).to eq(token[:ref])
749
+ end
750
+ end
751
+
752
+ describe '#logout' do
753
+ it 'performs logout' do
754
+ token = client.query { login @user[:ref], password: @password }
755
+ user_client = get_client secret: token[:secret]
756
+
757
+ expect(user_client.query { logout true }).to be(true)
758
+ end
759
+ end
760
+
761
+ describe '#identify' do
762
+ it 'performs identify' do
763
+ expect(client.query { identify(@user[:ref], @password) }).to be(true)
764
+ end
765
+ end
766
+
767
+ describe '#has_identity' do
768
+ it 'performs has_identity' do
769
+ token = client.query { login @user[:ref], password: @password }
770
+ user_client = get_client secret: token[:secret]
771
+
772
+ expect(client.query { has_identity }).to be(false)
773
+ expect(user_client.query { has_identity }).to be(true)
774
+ end
775
+ end
776
+
777
+ describe '#identity' do
778
+ it 'performs identity' do
779
+ token = client.query { login @user[:ref], password: @password }
780
+ user_client = get_client secret: token[:secret]
781
+
782
+ expect(user_client.query { identity }).to eq(@user[:ref])
783
+ end
784
+ end
785
+ end
786
+
787
+ describe '#concat' do
788
+ it 'performs concat' do
789
+ expect(client.query { concat ['a', 'b', 'c'] }).to eq('abc')
790
+ expect(client.query { concat [] }).to eq('')
791
+ end
792
+
793
+ it 'performs concat with separator' do
794
+ expect(client.query { concat(['a', 'b', 'c'], '.') }).to eq('a.b.c')
795
+ end
796
+ end
797
+
798
+ describe '#casefold' do
799
+ it 'performs casefold' do
800
+ expect(client.query { casefold 'Hen Wen' }).to eq('hen wen')
801
+
802
+ # https://unicode.org/reports/tr15/
803
+ expect(client.query { casefold("\u212B", "NFD") } ).to eq("A\u030A")
804
+ expect(client.query { casefold("\u212B", "NFC") } ).to eq("\u00C5")
805
+ expect(client.query { casefold("\u1E9B\u0323", "NFKD") } ).to eq("\u0073\u0323\u0307")
806
+ expect(client.query { casefold("\u1E9B\u0323", "NFKC") } ).to eq("\u1E69")
807
+
808
+ expect(client.query { casefold("\u212B", "NFKCCaseFold") } ).to eq("\u00E5")
809
+ end
810
+ end
811
+
812
+ describe '#ngram' do
813
+ it 'performs ngram' do
814
+ expect(client.query { ngram 'what' }).to eq(['w', 'wh', 'h', 'ha', 'a', 'at', 't'])
815
+ expect(client.query { ngram 'what', min: 2, max: 3 }).to eq(['wh', 'wha', 'ha', 'hat', 'at'])
816
+
817
+ expect(client.query { ngram ['john', 'doe'] }).to eq(['j', 'jo', 'o', 'oh', 'h', 'hn', 'n', 'd', 'do', 'o', 'oe', 'e'])
818
+ expect(client.query { ngram ['john', 'doe'], min: 3, max: 4 }).to eq(['joh', 'john', 'ohn', 'doe'])
819
+ end
820
+ end
821
+
822
+ describe '#test' do
823
+ it 'performs time' do
824
+ # `.round 9` is necessary because MRI 1.9.3 stores with greater precision than just nanoseconds.
825
+ # This cuts it down to just nanoseconds so that the times compare as equal.
826
+ time = Time.at(0, 123_456.789).round 9
827
+ expect(client.query { time '1970-01-01T00:00:00.123456789Z' }).to eq(time)
828
+
829
+ # 'now' refers to the current time.
830
+ expect(client.query { time 'now' }).to be_a(Time)
831
+ end
832
+ end
833
+
834
+ describe '#epoch' do
835
+ it 'performs epoch for seconds' do
836
+ secs = random_number
837
+ expect(client.query { epoch(secs, 'second') }).to eq(Time.at(secs).utc)
838
+ end
839
+
840
+ it 'performs epoch for nanoseconds' do
841
+ nanos = random_number
842
+ expect(client.query { epoch(nanos, 'nanosecond') }).to eq(Time.at(Rational(nanos, 1_000_000_000)).utc)
843
+ end
844
+ end
845
+
846
+ describe '#date' do
847
+ it 'performs date' do
848
+ expect(client.query { date('1970-01-01') }).to eq(Date.new(1970, 1, 1))
849
+ end
850
+ end
851
+
852
+ describe '#new_id' do
853
+ it 'gets a new id' do
854
+ expect(client.query { new_id }).to be_a(String)
855
+ end
856
+ end
857
+
858
+ describe '#database' do
859
+ it 'gets an existing database' do
860
+ # Create a database
861
+ name = random_string
862
+ ref = admin_client.query { create_database(name: name) }[:ref]
863
+
864
+ # Get the database ref
865
+ expect(admin_client.query { database(name) }).to eq(ref)
866
+ end
867
+ end
868
+
869
+ describe '#class_' do
870
+ it 'gets an existing class' do
871
+ # Create a class
872
+ name = random_string
873
+ ref = client.query { create_class(name: name) }[:ref]
874
+
875
+ # Get the class ref
876
+ expect(client.query { class_(name) }).to eq(ref)
877
+ end
878
+ end
879
+
880
+ describe '#index' do
881
+ it 'gets an existing index' do
882
+ # Create an index
883
+ class_ref = client.query { create_class(name: random_string) }[:ref]
884
+ name = random_string
885
+ ref = client.query { create_index(name: name, source: class_ref) }[:ref]
886
+
887
+ # Get the index ref
888
+ expect(client.query { index(name) }).to eq(ref)
889
+ end
890
+ end
891
+
892
+ describe '#function' do
893
+ it 'gets an existing function' do
894
+ # Create a function
895
+ name = random_string
896
+ ref = client.query { create_function(name: name, body: query(lambda { |a| add(a, 1) })) }[:ref]
897
+
898
+ # Get the function ref
899
+ expect(client.query { function(name) }).to eq(ref)
900
+ end
901
+ end
902
+
903
+ describe '#equals' do
904
+ it 'performs equals' do
905
+ expect(client.query { equals(1, 1, 1) }).to be(true)
906
+ expect(client.query { equals(1, 1, 2) }).to be(false)
907
+ expect(client.query { equals 1 }).to be(true)
908
+ end
909
+ end
910
+
911
+ describe '#contains' do
912
+ it 'performs contains' do
913
+ obj = { a: { b: 1 } }
914
+
915
+ expect(client.query { contains([:a, :b], obj) }).to be(true)
916
+ expect(client.query { contains(:a, obj) }).to be(true)
917
+ expect(client.query { contains([:a, :c], obj) }).to be(false)
918
+ end
919
+ end
920
+
921
+ describe '#select' do
922
+ it 'performs select with hash' do
923
+ obj = { a: { b: 1 } }
924
+
925
+ expect(client.query { select(:a, obj) }).to eq(b: 1)
926
+ expect(client.query { select([:a, :b], obj) }).to eq(1)
927
+ expect(client.query { select(:c, obj, default: nil) }).to be_nil
928
+ expect { client.query { select(:c, obj) } }.to raise_error(Fauna::NotFound)
929
+ end
930
+
931
+ it 'performs select with array' do
932
+ arr = [1, 2, 3]
933
+
934
+ expect(client.query { select(2, arr) }).to eq(3)
935
+ expect { client.query { select(3, arr) } }.to raise_error(Fauna::NotFound)
936
+ end
937
+ end
938
+
939
+ describe '#select_all' do
940
+ it 'performs select_all with hash' do
941
+ obj1 = { foo: 'bar' }
942
+ obj2 = { foo: 'baz' }
943
+
944
+ expect(client.query { select_all(:foo, [obj1, obj2]) }).to eq(['bar', 'baz'])
945
+ end
946
+
947
+ it 'performs select_all with array' do
948
+ obj1 = { foo: [0, 1] }
949
+ obj2 = { foo: [2, 3] }
950
+
951
+ expect(client.query { select_all([:foo, 0], [obj1, obj2]) }).to eq([0, 2])
952
+ end
953
+ end
954
+
955
+ describe '#add' do
956
+ it 'performs add' do
957
+ expect(client.query { add(2, 3, 5) }).to eq(10)
958
+ end
959
+ end
960
+
961
+ describe '#multiply' do
962
+ it 'performs multiply' do
963
+ expect(client.query { multiply(2, 3, 5) }).to eq(30)
964
+ end
965
+ end
966
+
967
+ describe '#subtract' do
968
+ it 'performs subtract' do
969
+ expect(client.query { subtract(2, 3, 5) }).to eq(-6)
970
+ expect(client.query { subtract(2) }).to eq(2)
971
+ end
972
+ end
973
+
974
+ describe '#divide' do
975
+ it 'performs divide' do
976
+ expect(client.query { divide(2.0, 3, 5) }).to eq(2.0 / 15)
977
+ expect(client.query { divide(2) }).to eq(2)
978
+ end
979
+ end
980
+
981
+ describe '#modulo' do
982
+ it 'performs modulo' do
983
+ expect(client.query { modulo(5, 2) }).to eq(1)
984
+ expect(client.query { modulo(15, 10, 2) }).to eq(1)
985
+ expect(client.query { modulo(2) }).to eq(2)
986
+ end
987
+ end
988
+
989
+ describe '#lt' do
990
+ it 'performs lt' do
991
+ expect(client.query { lt(1, 2) }).to be(true)
992
+ expect(client.query { lt(2, 2) }).to be(false)
993
+ end
994
+ end
995
+
996
+ describe '#lte' do
997
+ it 'performs lte' do
998
+ expect(client.query { lte(1, 1) }).to be(true)
999
+ expect(client.query { lte(2, 1) }).to be(false)
1000
+ end
1001
+ end
1002
+
1003
+ describe '#gt' do
1004
+ it 'performs gt' do
1005
+ expect(client.query { gt(2, 1) }).to be(true)
1006
+ expect(client.query { gt(2, 2) }).to be(false)
1007
+ end
1008
+ end
1009
+
1010
+ describe '#gte' do
1011
+ it 'performs gte' do
1012
+ expect(client.query { gte(2, 2) }).to be(true)
1013
+ expect(client.query { gte(2, 3) }).to be(false)
1014
+ end
1015
+ end
1016
+
1017
+ describe '#and_' do
1018
+ it 'performs and' do
1019
+ expect(client.query { and_(true, true, false) }).to be(false)
1020
+ expect(client.query { and_(true, true, true) }).to be(true)
1021
+ expect(client.query { and_(true) }).to be(true)
1022
+ expect(client.query { and_(false) }).to be(false)
1023
+ end
1024
+ end
1025
+
1026
+ describe '#or_' do
1027
+ it 'performs or' do
1028
+ expect(client.query { or_(false, false, true) }).to be(true)
1029
+ expect(client.query { or_(false, false, false) }).to be(false)
1030
+ expect(client.query { or_(true) }).to be(true)
1031
+ expect(client.query { or_(false) }).to be(false)
1032
+ end
1033
+ end
1034
+
1035
+ describe '#not_' do
1036
+ it 'performs not' do
1037
+ expect(client.query { not_(true) }).to be(false)
1038
+ expect(client.query { not_(false) }).to be(true)
1039
+ end
1040
+ end
1041
+
1042
+ describe '#to_string' do
1043
+ it 'performs to_string' do
1044
+ expect(client.query { to_string(42) }).to eq("42")
1045
+ end
1046
+ end
1047
+
1048
+ describe '#to_number' do
1049
+ it 'performs to_number' do
1050
+ expect(client.query { to_number("42") }).to be(42)
1051
+ end
1052
+ end
1053
+
1054
+ describe '#to_time' do
1055
+ it 'performs to_time' do
1056
+ expect(client.query { to_time("1970-01-01T00:00:00Z") }).to eq(Time.at(0).utc)
1057
+ end
1058
+ end
1059
+
1060
+ describe '#to_date' do
1061
+ it 'performs to_date' do
1062
+ expect(client.query { to_date("1970-01-01") }).to eq(Date.new(1970, 1, 1))
1063
+ end
1064
+ end
1065
+
1066
+ describe '#recursive references' do
1067
+ it 'create nested keys' do
1068
+ new_client = create_new_database(admin_client, 'db-for-keys')
1069
+ new_client.query { create_database name: 'db-test' }
1070
+
1071
+ server_key = new_client.query { create_key database: database('db-test'), role: 'server' }
1072
+ admin_key = new_client.query { create_key database: database('db-test'), role: 'admin' }
1073
+
1074
+ expect(new_client.query { paginate keys() }).to eq(data: [server_key[:ref], admin_key[:ref]])
1075
+ expect(admin_client.query { paginate keys(database('db-for-keys')) }).to eq(data: [server_key[:ref], admin_key[:ref]])
1076
+ end
1077
+
1078
+ it 'create nested class' do
1079
+ client1 = create_new_database(admin_client, 'parent-database')
1080
+ create_new_database(client1, 'child-database')
1081
+
1082
+ key = client1.query { create_key database: database('child-database'), role: 'server' }
1083
+ client2 = get_client secret: key[:secret]
1084
+ client2.query { create_class name: 'a_class' }
1085
+
1086
+ nested_database = Fauna::Query.database('child-database', Fauna::Query.database('parent-database'))
1087
+ nested_class = Fauna::Query.class_('a_class', nested_database)
1088
+ all_nested_classes = Fauna::Query.classes(nested_database)
1089
+
1090
+ parent_database_ref = Fauna::Ref.new('parent-database', Fauna::Native.databases)
1091
+ child_database_ref = Fauna::Ref.new('child-database', Fauna::Native.databases, parent_database_ref)
1092
+ a_class_ref = Fauna::Ref.new('a_class', Fauna::Native.classes, child_database_ref)
1093
+
1094
+ expect(client.query { exists nested_class }).to be(true)
1095
+ expect(client.query { paginate all_nested_classes }).to eq(data: [a_class_ref])
1096
+ end
1097
+ end
1098
+
1099
+ def create_new_database(client, name)
1100
+ client.query { create_database name: name }
1101
+ key = client.query { create_key database: database(name), role: 'admin' }
1102
+ get_client secret: key[:secret]
1103
+ end
1104
+ end