ruby-ladybug 0.1.0

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,36 @@
1
+ # -*- ruby -*-
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ require 'ladybug/connection'
6
+
7
+
8
+ RSpec.describe( Ladybug::Connection ) do
9
+
10
+ let( :db ) { Ladybug.database }
11
+
12
+
13
+ it "can set the maximum number of threads for execution" do
14
+ connection = db.connect
15
+
16
+ expect {
17
+ connection.max_num_threads_for_exec += 1
18
+ }.to change { connection.max_num_threads_for_exec }.by( 1 )
19
+ end
20
+
21
+
22
+ it "shows the number of threads used for execution when inspected" do
23
+ connection = db.connect
24
+
25
+ expect( connection.inspect ).to match( /threads:\d+/i )
26
+ end
27
+
28
+
29
+ it "knows what database it's a connection for" do
30
+ connection = db.connect
31
+
32
+ expect( connection.database ).to eq( db )
33
+ end
34
+
35
+ end
36
+
@@ -0,0 +1,57 @@
1
+ # -*- ruby -*-
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ require 'ladybug/database'
6
+
7
+
8
+ RSpec.describe( Ladybug::Database ) do
9
+
10
+ let( :spec_tmpdir ) do
11
+ path = tmpfile_pathname()
12
+ path.mkpath
13
+ return path
14
+ end
15
+
16
+ let( :db_path ) { spec_tmpdir + 'spec_db' }
17
+
18
+
19
+ after( :each ) do
20
+ GC.start
21
+ end
22
+
23
+ it "can be created in-memory" do
24
+ instance = described_class.new( '' )
25
+ expect( instance ).to be_a( described_class )
26
+ end
27
+
28
+
29
+ it "can be created read-only from an existing on-disk database" do
30
+ _original = described_class.new( db_path.to_s )
31
+
32
+ ro = described_class.new( db_path.to_s, read_only: true )
33
+ expect( ro ).to be_read_only
34
+ end
35
+
36
+
37
+ it "can be created without auto-checkpointing" do
38
+ instance = described_class.new( db_path.to_s, auto_checkpoint: false )
39
+ expect( instance ).not_to be_auto_checkpointing
40
+ end
41
+
42
+
43
+ it "can be created without compression" do
44
+ instance = described_class.new( db_path.to_s, enable_compression: false )
45
+ expect( instance ).not_to be_compression_enabled
46
+ end
47
+
48
+
49
+ it "can create a connection to itself" do
50
+ instance = described_class.new( db_path.to_s, enable_compression: false )
51
+
52
+ result = instance.connect
53
+
54
+ expect( result ).to be_a( Ladybug::Connection )
55
+ end
56
+
57
+ end
@@ -0,0 +1,91 @@
1
+ # -*- ruby -*-
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ require 'ladybug/prepared_statement'
6
+
7
+
8
+ RSpec.describe( Ladybug::PreparedStatement ) do
9
+
10
+ let( :db ) { Ladybug.database }
11
+ let( :connection ) { db.connect }
12
+
13
+ let( :schema ) { load_test_data( 'demo-db/schema.cypher' ) }
14
+ let( :copy_statements ) { load_test_data( 'demo-db/copy.cypher' ) }
15
+
16
+
17
+ def setup_demo_db
18
+ connection.run( schema )
19
+ connection.run( copy_statements )
20
+ end
21
+
22
+
23
+ before( :each ) do
24
+ setup_demo_db()
25
+ end
26
+
27
+
28
+ #
29
+ # Specs
30
+ #
31
+
32
+ it "can be created via a connection and a query string" do
33
+ statement = described_class.new( connection, <<~END_OF_QUERY )
34
+ MATCH (u:User)
35
+ WHERE u.age > $min_age and u.age < $max_age
36
+ RETURN u.name
37
+ END_OF_QUERY
38
+
39
+ expect( statement ).to be_a( described_class )
40
+ expect( statement ).to be_success
41
+ expect( statement.connection ).to be( connection )
42
+ end
43
+
44
+
45
+ it "errors if executed before its variables are bound" do
46
+ statement = described_class.new( connection, <<~END_OF_QUERY )
47
+ MATCH (u:User)
48
+ WHERE u.age > $min_age and u.age < $max_age
49
+ RETURN u.name
50
+ END_OF_QUERY
51
+
52
+ expect {
53
+ statement.execute
54
+ }.to raise_error( Ladybug::QueryError, /parameter \w+ not found/i )
55
+ end
56
+
57
+
58
+ it "can be executed if all of its variables are bound" do
59
+ statement = described_class.new( connection, <<~END_OF_QUERY )
60
+ MATCH (u:User)
61
+ WHERE u.age >= $min_age and u.age <= $max_age
62
+ RETURN u.name
63
+ END_OF_QUERY
64
+
65
+ result = statement.execute( min_age: 40, max_age: 50 )
66
+
67
+ expect( result.num_tuples ).to eq( 2 )
68
+
69
+ result.finish
70
+ end
71
+
72
+
73
+ it "can be reused" do
74
+ statement = described_class.new( connection, <<~END_OF_QUERY )
75
+ MATCH (u:User)
76
+ WHERE u.age >= $min_age and u.age <= $max_age
77
+ RETURN u.name
78
+ END_OF_QUERY
79
+
80
+ result = statement.execute( min_age: 40, max_age: 50 )
81
+ expect( result ).to be_success
82
+ expect( result.num_tuples ).to eq( 2 )
83
+ result.finish
84
+
85
+ result = statement.execute( min_age: 5, max_age: 90 )
86
+ expect( result ).to be_success
87
+ expect( result.num_tuples ).to eq( 4 )
88
+ result.finish
89
+ end
90
+
91
+ end
@@ -0,0 +1,30 @@
1
+ # -*- ruby -*-
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ require 'ladybug/query_summary'
6
+
7
+
8
+ RSpec.describe( Ladybug::QuerySummary ) do
9
+
10
+ CREATE_TABLE_STATEMENTS = [
11
+ 'CREATE NODE TABLE User(name STRING, age INT64, PRIMARY KEY (name))',
12
+ 'CREATE NODE TABLE City(name STRING, population INT64, PRIMARY KEY (name))',
13
+ ]
14
+
15
+
16
+ let( :db ) { Ladybug.database }
17
+ let( :connection ) { db.connect }
18
+
19
+
20
+ it "can return a summary of its query's timing" do
21
+ connection.query( CREATE_TABLE_STATEMENTS.first ) do |result|
22
+ summary = result.query_summary
23
+
24
+ expect( summary ).to be_a( described_class )
25
+ expect( summary.compiling_time ).to be_a( Float ).and( be > 0.0 )
26
+ expect( summary.execution_time ).to be_a( Float ).and( be > 0.0 )
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,225 @@
1
+ # -*- ruby -*-
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ require 'ladybug/result'
6
+
7
+
8
+ RSpec.describe( Ladybug::Result ) do
9
+
10
+ let( :db ) { Ladybug.database }
11
+ let( :connection ) { db.connect }
12
+
13
+ let( :schema ) { load_test_data( 'demo-db/schema.cypher' ) }
14
+ let( :copy_statements ) { load_test_data( 'demo-db/copy.cypher' ) }
15
+
16
+
17
+ def setup_demo_db
18
+ connection.run( schema )
19
+ connection.run( copy_statements )
20
+ end
21
+
22
+
23
+ #
24
+ # Specs
25
+ #
26
+
27
+ describe "query constructor" do
28
+
29
+ it "can be created via a simple query" do
30
+ result = described_class.from_query( connection, schema.each_line.first )
31
+
32
+ expect( result ).to be_a( described_class )
33
+ expect( result ).to be_success
34
+
35
+ result.finish
36
+ end
37
+
38
+
39
+ it "automatically finishes the result when run with a block" do
40
+ block_result = nil
41
+
42
+ described_class.from_query( connection, schema.each_line.first ) do |result|
43
+ block_result = result
44
+ expect( result ).to be_a( described_class )
45
+ expect( result ).to be_success
46
+ end
47
+
48
+ expect( block_result ).to be_finished
49
+ end
50
+
51
+
52
+ it "can return a summary of its query's timing" do
53
+ result = described_class.from_query( connection, schema.each_line.first )
54
+ summary = result.query_summary
55
+
56
+ expect( summary ).to be_a( Ladybug::QuerySummary )
57
+
58
+ result.finish
59
+ end
60
+
61
+
62
+ it "can return the error message if there was a problem with the query" do
63
+ expect {
64
+ described_class.from_query( connection, "FOOBANGLE NERFRIDER" )
65
+ }.to raise_error( Ladybug::QueryError, /parser exception/i )
66
+ end
67
+
68
+
69
+ it "can iterate over result tuples" do
70
+ setup_demo_db()
71
+
72
+ result = described_class.from_query( connection, <<~END_OF_QUERY )
73
+ MATCH ( a:User )-[ f:Follows ]->( b:User )
74
+ RETURN a.name, b.name, f.since;
75
+ END_OF_QUERY
76
+
77
+ expect( result ).to be_a( described_class )
78
+ expect( result ).to be_success
79
+ expect( result.num_columns ).to eq( 3 )
80
+ expect( result.column_names ).to eq([ "a.name", "b.name", "f.since" ])
81
+ expect( result.to_a ).to eq([
82
+ { "a.name" => "Adam", "b.name" => "Karissa", "f.since" => 2020 },
83
+ { "a.name" => "Adam", "b.name" => "Zhang", "f.since" => 2020 },
84
+ { "a.name" => "Karissa", "b.name" => "Zhang", "f.since" => 2021 },
85
+ { "a.name" => "Zhang", "b.name" => "Noura", "f.since" => 2022 }
86
+ ])
87
+
88
+ result.finish
89
+ end
90
+
91
+
92
+ it "handles a #next after it finishes iteration over the current set" do
93
+ setup_demo_db()
94
+
95
+ result = described_class.from_query( connection, <<~END_OF_QUERY )
96
+ MATCH ( a:User )-[ f:Follows ]->( b:User )
97
+ RETURN a.name, b.name, f.since;
98
+ END_OF_QUERY
99
+
100
+ result.tuples # Iterate over all tuples
101
+
102
+ expect( result.next ).to be_nil
103
+
104
+ result.finish
105
+ end
106
+
107
+
108
+ it "can fetch individual result tuples via the index operator" do
109
+ setup_demo_db()
110
+
111
+ result = described_class.from_query( connection, <<~END_OF_QUERY )
112
+ MATCH ( a:User )-[ f:Follows ]->( b:User )
113
+ RETURN a.name, b.name, f.since;
114
+ END_OF_QUERY
115
+
116
+ tuples = result.to_a
117
+
118
+ expect( result[0] ).to eq( tuples[0] )
119
+ expect( result[1] ).to eq( tuples[1] )
120
+ expect( result[2] ).to eq( tuples[2] )
121
+ expect( result[3] ).to eq( tuples[3] )
122
+
123
+ result.finish
124
+ end
125
+
126
+
127
+ it "can iterate over result sets" do
128
+ result = described_class.from_query( connection, <<~END_QUERY )
129
+ return 1;
130
+ return 2;
131
+ return 3;
132
+ END_QUERY
133
+
134
+ rval = result.each_set.flat_map do |subset|
135
+ subset.to_a
136
+ end
137
+
138
+ expect( rval ).to eq([
139
+ { "1" => 1 },
140
+ { "2" => 2 },
141
+ { "3" => 3 },
142
+ ])
143
+
144
+ result.finish
145
+ end
146
+
147
+
148
+ it "also finishes successive results if they've been fetched when the first one is finished" do
149
+ result = described_class.from_query( connection, <<~END_QUERY )
150
+ return 1;
151
+ return 2;
152
+ return 3;
153
+ END_QUERY
154
+
155
+ second_result = result.next_set
156
+
157
+ result.finish
158
+
159
+ expect( second_result ).to be_finished
160
+ end
161
+
162
+
163
+ it "errors if it's used after being finished" do
164
+ result = described_class.from_query( connection, "return 1;" )
165
+ result.finish
166
+
167
+ expect( result ).to be_finished
168
+
169
+ expect {
170
+ result.get_next_values
171
+ }.to raise_error( Ladybug::FinishedError )
172
+ end
173
+
174
+ end
175
+
176
+
177
+ describe "prepared statement constructor" do
178
+
179
+ before( :each ) do
180
+ setup_demo_db()
181
+ end
182
+
183
+
184
+ it "can be created via a prepared statement" do
185
+ statement = Ladybug::PreparedStatement.new( connection, <<~END_OF_QUERY )
186
+ MATCH (u:User)
187
+ WHERE u.age > $min_age and u.age < $max_age
188
+ RETURN u.name
189
+ END_OF_QUERY
190
+ statement.bind( min_age: 32, max_age: 46 )
191
+
192
+ result = described_class.from_prepared_statement( statement )
193
+
194
+ expect( result ).to be_a( described_class )
195
+ expect( result ).to be_success
196
+
197
+ result.finish
198
+ end
199
+
200
+
201
+ it "is automatically finished if constructed with a block" do
202
+ statement = Ladybug::PreparedStatement.new( connection, <<~END_OF_QUERY )
203
+ MATCH (u:User)
204
+ WHERE u.age > $min_age and u.age < $max_age
205
+ RETURN u.name
206
+ END_OF_QUERY
207
+ statement.bind( min_age: 32, max_age: 46 )
208
+
209
+ block_result = nil
210
+ rval = described_class.from_prepared_statement( statement ) do |result|
211
+ block_result = result
212
+ expect( result ).to be_a( described_class )
213
+ expect( result ).to be_success
214
+
215
+ result.first['u.name']
216
+ end
217
+
218
+ expect( block_result ).to be_finished
219
+ expect( rval ).to eq( "Karissa" )
220
+ end
221
+
222
+ end
223
+
224
+
225
+ end