ruby-kuzu 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/History.md +17 -0
- data/README.md +135 -11
- data/ext/kuzu_ext/kuzu_ext.h +4 -2
- data/ext/kuzu_ext/prepared_statement.c +1 -1
- data/ext/kuzu_ext/query_summary.c +1 -1
- data/ext/kuzu_ext/result.c +1 -1
- data/ext/kuzu_ext/types.c +5 -2
- data/lib/kuzu/result.rb +44 -3
- data/lib/kuzu.rb +22 -2
- data/spec/kuzu/database_spec.rb +7 -1
- data/spec/kuzu/result_spec.rb +16 -0
- data/spec/kuzu/types_spec.rb +30 -0
- data/spec/kuzu_spec.rb +30 -2
- data/spec/spec_helper.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +3 -3
- metadata.gz.sig +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ffa06875d65096bb5bac251440d1f1ec56d233a7675f86b9e4d22a5039d91020
|
4
|
+
data.tar.gz: 4666b63219b97e6fcb8781995bbb71bbb486a09f8bdbbe763e6f4b225101750a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd651f4b5f1267a5e1725cc692ca3554842d58c4cb71bb63498172de7d6fcef6df3d7855bbdba2aadd2f35a01482e73104ef2a9647bf4a8221b3dba63e821096
|
7
|
+
data.tar.gz: b65eb14735f2d06883cc4392e67e4cb208fb415ac9de62a24d1e686bad95fa7fb0412124ef01908ce587a223bc1bcb37688d8cb4df10564f888acde3192171bd
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/History.md
CHANGED
@@ -1,6 +1,23 @@
|
|
1
1
|
# Release History for ruby-kuzu
|
2
2
|
|
3
3
|
---
|
4
|
+
|
5
|
+
## v0.2.0 [2025-07-16] Michael Granger <ged@FaerieMUD.org>
|
6
|
+
|
7
|
+
Enhancements:
|
8
|
+
|
9
|
+
- Add support for the SERIAL type.
|
10
|
+
- Add Kuzu.is_database? method
|
11
|
+
- Flesh out documentation
|
12
|
+
|
13
|
+
Bugfixes:
|
14
|
+
|
15
|
+
- Fixups for Linux and Kuzu 0.11
|
16
|
+
- Fix storage version check (0.11+)
|
17
|
+
- Fix a bug in Result#next
|
18
|
+
- Fix Date type conversion
|
19
|
+
|
20
|
+
|
4
21
|
## v0.1.0 [2025-06-17] Michael Granger <ged@FaerieMUD.org>
|
5
22
|
|
6
23
|
Enhancements:
|
data/README.md
CHANGED
@@ -47,15 +47,129 @@ Once you have a Kuzu::Database object, you need a connection to actually use it:
|
|
47
47
|
# => #<Kuzu::Connection:0x0000000122a41f28 threads:16>
|
48
48
|
|
49
49
|
|
50
|
-
|
51
50
|
### Querying
|
52
51
|
|
52
|
+
There are two ways of running queries, immediate execution and via a prepared
|
53
|
+
statement. Either method returns one or more results as Kuzu::Result objects.
|
54
|
+
|
55
|
+
|
53
56
|
### Results
|
54
57
|
|
58
|
+
The each result of a query in a query string is returned as a Kuzu::Result. If
|
59
|
+
there are more than one queries in the query string, the Results will be
|
60
|
+
chained together so they can be iterated over.
|
61
|
+
|
62
|
+
Each tuple in a Result can be fetched using Kuzu::Result#next, which returns a
|
63
|
+
Hash of the variables in the `RETURN` clause, keyed by the column name as a
|
64
|
+
`String`:
|
65
|
+
|
66
|
+
res = conn.query( 'MATCH (a:User)-[f:Follows]->(b:User) RETURN a.name, b.name, f.since;' )
|
67
|
+
# => #<Kuzu::Result:0x00000001268449d8 success: true (4 tuples of 3 columns)>
|
68
|
+
res.next
|
69
|
+
# => {"a.name" => "Adam", "b.name" => "Karissa", "f.since" => 2020}
|
70
|
+
res.next
|
71
|
+
# => {"a.name" => "Adam", "b.name" => "Zhang", "f.since" => 2020}
|
72
|
+
|
73
|
+
The value's type will be determined by the Kuzu datatype of that column of the
|
74
|
+
result. See Kuzu::Result for a mapping of the types.
|
75
|
+
|
76
|
+
If there are more tuples to fetch, Kuzu::Result#has_next? will return `true`:
|
77
|
+
|
78
|
+
res.has_next?
|
79
|
+
# => true
|
80
|
+
res.next
|
81
|
+
# => {"a.name" => "Karissa", "b.name" => "Zhang", "f.since" => 2021}
|
82
|
+
res.next
|
83
|
+
# => {"a.name" => "Zhang", "b.name" => "Noura", "f.since" => 2022}
|
84
|
+
res.has_next?
|
85
|
+
# => false
|
86
|
+
res.next
|
87
|
+
# => nil
|
88
|
+
|
89
|
+
Kuzu::Result is also Enumerable, so you can `#each` over its tuples:
|
90
|
+
|
91
|
+
res.each
|
92
|
+
# => #<Enumerator: ...>
|
93
|
+
res.map do |tuple|
|
94
|
+
"%s has followed %s since %s" % tuple.values_at('a.name', 'b.name', 'f.since')
|
95
|
+
end
|
96
|
+
# => ["Adam has followed Karissa since 2020",
|
97
|
+
# "Adam has followed Zhang since 2020",
|
98
|
+
# "Karissa has followed Zhang since 2021",
|
99
|
+
# "Zhang has followed Noura since 2022"]
|
100
|
+
|
101
|
+
If the query string has more than one query in it, a separate Kuzu::Result is
|
102
|
+
linked to the previous one, and can be fetched using Kuzu::Result#next_set.
|
103
|
+
|
104
|
+
For example:
|
105
|
+
|
106
|
+
res = conn.query( <<~END_OF_QUERY )
|
107
|
+
MATCH (a:User)-[f:Follows]->(b:User)
|
108
|
+
RETURN a.name, b.name, f.since;
|
109
|
+
MATCH (u:User) RETURN *
|
110
|
+
END_OF_QUERY
|
111
|
+
# => #<Kuzu::Result:0x000000011e6faaf8 success: true (4 tuples of 3 columns)>
|
112
|
+
res.to_a
|
113
|
+
# => [{"a.name" => "Adam", "b.name" => "Karissa", "f.since" => 2020},
|
114
|
+
# {"a.name" => "Adam", "b.name" => "Zhang", "f.since" => 2020},
|
115
|
+
# {"a.name" => "Karissa", "b.name" => "Zhang", "f.since" => 2021},
|
116
|
+
# {"a.name" => "Zhang", "b.name" => "Noura", "f.since" => 2022}]
|
117
|
+
res.has_next_set?
|
118
|
+
# => true
|
119
|
+
res2 = res.next_set
|
120
|
+
# => #<Kuzu::Result:0x000000011e338fa0 success: true (4 tuples of 1 columns)>
|
121
|
+
res2.to_a
|
122
|
+
# => [{"u" => #<Kuzu::Node:0x000000011e7553b8 @id=[0, 0], @label="User",
|
123
|
+
# @properties={name: "Adam", age: 30}>},
|
124
|
+
# {"u" => #<Kuzu::Node:0x000000011e7551b0 @id=[0, 1], @label="User",
|
125
|
+
# @properties={name: "Karissa", age: 40}>},
|
126
|
+
# {"u" => #<Kuzu::Node:0x000000011e755048 @id=[0, 2], @label="User",
|
127
|
+
# @properties={name: "Zhang", age: 50}>},
|
128
|
+
# {"u" => #<Kuzu::Node:0x000000011e754ee0 @id=[0, 3], @label="User",
|
129
|
+
# @properties={name: "Noura", age: 25}>}]
|
130
|
+
res2.has_next_set?
|
131
|
+
# => false
|
132
|
+
res2.next_set
|
133
|
+
# => nil
|
134
|
+
|
135
|
+
|
55
136
|
### Prepared Statements
|
56
137
|
|
57
|
-
|
138
|
+
An alternative to using strings to query the database is to used *prepared statements*. These have a number of advantages, such as reusability and using parameters for queries instead of string interpolation.
|
139
|
+
|
140
|
+
You can create a Kuzu::PreparedStatement by calling Kuzu::Connection#prepare, then execute it one or more times with parameters using Kuzu::PreparedStatement#execute:
|
141
|
+
|
142
|
+
stmt = conn.prepare( <<~END_OF_QUERY )
|
143
|
+
MATCH (a:User)-[f:Follows]->(b:User)
|
144
|
+
WHERE a.name = $name
|
145
|
+
RETURN a.name, b.name, f.since'
|
146
|
+
END_OF_QUERY
|
147
|
+
# => #<Kuzu::PreparedStatement:0x000000011e919b68>
|
148
|
+
res = stmt.execute( name: "Karissa" )
|
149
|
+
# => #<Kuzu::Result:0x000000011ee8df90 success: true (1 tuples of 3 columns)>
|
150
|
+
res.to_a
|
151
|
+
# => [{"a.name" => "Karissa", "b.name" => "Zhang", "f.since" => 2021}]
|
152
|
+
|
58
153
|
|
154
|
+
### Result Memory Management
|
155
|
+
|
156
|
+
Because of the way Ruby frees memory when it's shutting down (i.e., the order is indeterminate), Kuzu::Result objects may not be freed immediately when they go out of scope. To provide some way to manage this, the Kuzu::Result#finish call is provided as a way to explicitly destroy the underlying Kuzu data structure so the Result can be freed. Since this is somewhat inconvenient to manage, there are two forms of Kuzu::Connection#query and Kuzu::PreparedStatement#execute, one which returns a Result and a "bang" equivalent one which just returns success or failure. Additionally, passing a block to either method will yield the Result to the block and then immediately `finish` the Result for you and return the block's value.
|
157
|
+
|
158
|
+
query_string = 'MATCH (a:User)-[f:Follows]->(b:User) RETURN a.name, b.name, f.since'
|
159
|
+
conn.query!( query_string )
|
160
|
+
# => true
|
161
|
+
conn.query( query_string ) {|res| res.tuples }
|
162
|
+
# => [{"a.name" => "Adam", "b.name" => "Karissa", "f.since" => 2020},
|
163
|
+
# {"a.name" => "Adam", "b.name" => "Zhang", "f.since" => 2020},
|
164
|
+
# {"a.name" => "Karissa", "b.name" => "Zhang", "f.since" => 2021},
|
165
|
+
# {"a.name" => "Zhang", "b.name" => "Noura", "f.since" => 2022}]
|
166
|
+
|
167
|
+
stmt = conn.prepare( "CREATE (:User {name: $name, age: $age})" )
|
168
|
+
# => #<Kuzu::PreparedStatement:0x000000010bc7cfe8>
|
169
|
+
stmt.execute!( name: 'David', age: 19 )
|
170
|
+
# => true
|
171
|
+
stmt.execute!( name: 'Agnes', age: 28 )
|
172
|
+
# => true
|
59
173
|
|
60
174
|
|
61
175
|
## Examples
|
@@ -64,26 +178,36 @@ Once you have a Kuzu::Database object, you need a connection to actually use it:
|
|
64
178
|
|
65
179
|
db = Kuzu.database
|
66
180
|
conn = db.connect
|
67
|
-
conn.
|
68
|
-
conn.
|
69
|
-
conn.
|
70
|
-
conn.
|
181
|
+
conn.run("CREATE NODE TABLE User(name STRING, age INT64, PRIMARY KEY (name))")
|
182
|
+
conn.run("CREATE NODE TABLE City(name STRING, population INT64, PRIMARY KEY (name))")
|
183
|
+
conn.run("CREATE REL TABLE Follows(FROM User TO User, since INT64)")
|
184
|
+
conn.run("CREATE REL TABLE LivesIn(FROM User TO City)")
|
71
185
|
|
72
186
|
# Load data.
|
73
|
-
conn.
|
74
|
-
conn.
|
75
|
-
conn.
|
76
|
-
conn.
|
187
|
+
conn.run("COPY User FROM \"user.csv\"")
|
188
|
+
conn.run("COPY City FROM \"city.csv\"")
|
189
|
+
conn.run("COPY Follows FROM \"follows.csv\"")
|
190
|
+
conn.run("COPY LivesIn FROM \"lives-in.csv\"")
|
77
191
|
|
78
192
|
# Execute a simple query.
|
79
193
|
result = conn.query("MATCH (a:User)-[f:Follows]->(b:User) RETURN a.name, f.since, b.name;")
|
80
194
|
|
81
195
|
# Output query result.
|
82
196
|
result.each do |tuple|
|
83
|
-
name, since, name2 = tuple.values_at(
|
197
|
+
name, since, name2 = tuple.values_at( 'a.name', 'f.since', 'b.name' )
|
84
198
|
puts "%s follows %s since %lld", [ name, name2, since ]
|
85
199
|
end
|
86
200
|
|
201
|
+
result.finish
|
202
|
+
|
203
|
+
|
204
|
+
## To-Do List
|
205
|
+
|
206
|
+
- `UNION` result type.
|
207
|
+
- `JSON` result type from the JSON extension
|
208
|
+
- Better memory management for Kuzu::Results
|
209
|
+
|
210
|
+
|
87
211
|
## Requirements
|
88
212
|
|
89
213
|
- Ruby >= 3
|
data/ext/kuzu_ext/kuzu_ext.h
CHANGED
@@ -28,8 +28,10 @@
|
|
28
28
|
#ifdef HAVE_STDARG_PROTOTYPES
|
29
29
|
#include <stdarg.h>
|
30
30
|
#define va_init_list(a, b) va_start (a, b)
|
31
|
-
void rkuzu_log_obj (VALUE, const char *, const char *, ...)
|
32
|
-
|
31
|
+
void rkuzu_log_obj (VALUE, const char *, const char *, ...)
|
32
|
+
__attribute__ ((format (printf, 2, 0)));
|
33
|
+
void rkuzu_log (const char *, const char *, ...)
|
34
|
+
__attribute__ ((format (printf, 1, 0)));
|
33
35
|
#else
|
34
36
|
#include <varargs.h>
|
35
37
|
#define va_init_list(a, b) va_start (a)
|
@@ -43,7 +43,7 @@ rkuzu_get_prepared_statement( VALUE prepared_statement_obj )
|
|
43
43
|
* Allocation function
|
44
44
|
*/
|
45
45
|
static rkuzu_prepared_statement *
|
46
|
-
rkuzu_prepared_statement_alloc()
|
46
|
+
rkuzu_prepared_statement_alloc( void )
|
47
47
|
{
|
48
48
|
rkuzu_prepared_statement *ptr = ALLOC( rkuzu_prepared_statement );
|
49
49
|
|
@@ -51,7 +51,7 @@ rkuzu_query_summary_s_allocate( VALUE klass )
|
|
51
51
|
* call-seq:
|
52
52
|
* Kuzu::QuerySummary.from_result( result ) -> query_summary
|
53
53
|
*
|
54
|
-
* Return a Kuzu::QuerySummary from a Kuzu::
|
54
|
+
* Return a Kuzu::QuerySummary from a Kuzu::Result.
|
55
55
|
*
|
56
56
|
*/
|
57
57
|
static VALUE
|
data/ext/kuzu_ext/result.c
CHANGED
@@ -408,7 +408,7 @@ rkuzu_result_get_column_names( VALUE self )
|
|
408
408
|
|
409
409
|
for ( uint64_t i = 0 ; i < col_count ; i++ ) {
|
410
410
|
if ( kuzu_query_result_get_column_name(&result->result, i, &name) != KuzuSuccess ) {
|
411
|
-
rb_raise( rkuzu_eError, "couldn't fetch name of column %
|
411
|
+
rb_raise( rkuzu_eError, "couldn't fetch name of column %lu", i );
|
412
412
|
}
|
413
413
|
rb_ary_push( rval, rb_str_new2(name) );
|
414
414
|
}
|
data/ext/kuzu_ext/types.c
CHANGED
@@ -165,8 +165,8 @@ rkuzu_convert_date( kuzu_value *value )
|
|
165
165
|
|
166
166
|
kuzu_date_to_tm( typed_value, &time );
|
167
167
|
|
168
|
-
argv[0] = INT2FIX( time.tm_year );
|
169
|
-
argv[1] = INT2FIX( time.tm_mon );
|
168
|
+
argv[0] = INT2FIX( time.tm_year + 1900 );
|
169
|
+
argv[1] = INT2FIX( time.tm_mon + 1 );
|
170
170
|
argv[2] = INT2FIX( time.tm_mday );
|
171
171
|
|
172
172
|
return rb_class_new_instance( 3, argv, rkuzu_rb_cDate );
|
@@ -554,6 +554,9 @@ rkuzu_convert_kuzu_value_to_ruby( kuzu_data_type_id type_id, kuzu_value *value )
|
|
554
554
|
case KUZU_UINT8: return rkuzu_convert_uint8( value );
|
555
555
|
case KUZU_INT128: return rkuzu_convert_int128( value );
|
556
556
|
|
557
|
+
// Serials just come out as int64s
|
558
|
+
case KUZU_SERIAL: return rkuzu_convert_int64( value );
|
559
|
+
|
557
560
|
case KUZU_DOUBLE: return rkuzu_convert_double( value );
|
558
561
|
case KUZU_FLOAT: return rkuzu_convert_float( value );
|
559
562
|
|
data/lib/kuzu/result.rb
CHANGED
@@ -6,6 +6,47 @@ require 'kuzu' unless defined?( Kuzu )
|
|
6
6
|
|
7
7
|
|
8
8
|
# Kùzu query result class
|
9
|
+
#
|
10
|
+
# These objects contain one result set from either a Kuzu::Connection#query call
|
11
|
+
# or Kuzu::PreparedStatement#execute. If there are multiple result sets, you can
|
12
|
+
# fetch the next one by calling Kuzu::Result#next_set. You can use #has_next_set?
|
13
|
+
# to test for a following set.
|
14
|
+
#
|
15
|
+
# Tuple values are converted to corresponding Ruby objects:
|
16
|
+
#
|
17
|
+
# | Kuzu Type | Ruby Type |
|
18
|
+
# | --------------- | ----------------------------------------------- |
|
19
|
+
# | +INT8+ | +Integer+ |
|
20
|
+
# | +INT16+ | +Integer+ |
|
21
|
+
# | +INT32+ | +Integer+ |
|
22
|
+
# | +INT64+ | +Integer+ |
|
23
|
+
# | +INT128+ | +Integer+ |
|
24
|
+
# | +UINT8+ | +Integer+ |
|
25
|
+
# | +UINT16+ | +Integer+ |
|
26
|
+
# | +UINT32+ | +Integer+ |
|
27
|
+
# | +UINT64+ | +Integer+ |
|
28
|
+
# | +FLOAT+ | +Float+ |
|
29
|
+
# | +DOUBLE+ | +Float+ |
|
30
|
+
# | +DECIMAL+ | +Float+ |
|
31
|
+
# | +BOOLEAN+ | +TrueClass+ or +FalseClass+ |
|
32
|
+
# | +UUID+ | +String+ (UTF-8 encoding) |
|
33
|
+
# | +STRING+ | +String+ (UTF-8 encoding) |
|
34
|
+
# | +NULL+ | +NilClass+ |
|
35
|
+
# | +DATE+ | +Date+ |
|
36
|
+
# | +TIMESTAMP+ | +Time+ |
|
37
|
+
# | +INTERVAL+ | +Float+ (interval in seconds) |
|
38
|
+
# | +STRUCT+ | +OpenStruct+ via the +ostruct+ standard library |
|
39
|
+
# | +MAP+ | +Hash+ |
|
40
|
+
# | +UNION+ | (not yet handled) |
|
41
|
+
# | +BLOB+ | +String+ (+ASCII_8BIT+ encoding) |
|
42
|
+
# | +SERIAL+ | +Integer+ |
|
43
|
+
# | +NODE+ | Kuzu::Node |
|
44
|
+
# | +REL+ | Kuzu::Rel |
|
45
|
+
# | +RECURSIVE_REL+ | Kuzu::RecursiveRel |
|
46
|
+
# | +LIST+ | +Array+ |
|
47
|
+
# | +ARRAY+ | +Array+ |
|
48
|
+
#
|
49
|
+
#
|
9
50
|
class Kuzu::Result
|
10
51
|
extend Loggability
|
11
52
|
|
@@ -60,7 +101,8 @@ class Kuzu::Result
|
|
60
101
|
|
61
102
|
### Get the next tuple of the result as a Hash.
|
62
103
|
def next
|
63
|
-
|
104
|
+
values = self.get_next_values or return nil
|
105
|
+
pairs = self.column_names.zip( values )
|
64
106
|
return Hash[ pairs ]
|
65
107
|
end
|
66
108
|
|
@@ -110,11 +152,10 @@ class Kuzu::Result
|
|
110
152
|
if self.finished?
|
111
153
|
details = " (finished)"
|
112
154
|
else
|
113
|
-
details = " success: %p (%d tuples of %d columns)
|
155
|
+
details = " success: %p (%d tuples of %d columns)" % [
|
114
156
|
self.success?,
|
115
157
|
self.num_tuples,
|
116
158
|
self.num_columns,
|
117
|
-
self.to_s,
|
118
159
|
]
|
119
160
|
end
|
120
161
|
|
data/lib/kuzu.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# -*- ruby -*-
|
2
2
|
|
3
|
+
require 'pathname'
|
3
4
|
require 'loggability'
|
4
5
|
|
5
6
|
require_relative 'kuzu_ext'
|
@@ -11,7 +12,10 @@ module Kuzu
|
|
11
12
|
|
12
13
|
|
13
14
|
# Library version
|
14
|
-
VERSION = '0.
|
15
|
+
VERSION = '0.2.0'
|
16
|
+
|
17
|
+
# Name of the file to look for when testing a path to see if it's a Kuzu database.
|
18
|
+
KUZU_CATALOG_FILENAME = 'data.kz'
|
15
19
|
|
16
20
|
|
17
21
|
# Set up a logger for Kuzu classes
|
@@ -52,6 +56,23 @@ module Kuzu
|
|
52
56
|
end
|
53
57
|
|
54
58
|
|
59
|
+
### Returns +true+ if the specified +pathname+ appears to be a valid Kuzu database
|
60
|
+
### for the current version of the storage format.
|
61
|
+
def self::is_database?( pathname )
|
62
|
+
pathname = Pathname( pathname )
|
63
|
+
if Kuzu.storage_version <= 38
|
64
|
+
return false unless pathname.directory?
|
65
|
+
testfile = pathname / KUZU_CATALOG_FILENAME
|
66
|
+
return testfile.exist?
|
67
|
+
else
|
68
|
+
return false unless pathname.file?
|
69
|
+
magic = pathname.read( 5 )
|
70
|
+
return magic[0, 4] == 'KUZU' && magic[4, 1].ord == Kuzu.storage_version
|
71
|
+
end
|
72
|
+
end
|
73
|
+
singleton_class.alias_method( :is_kuzu_database?, :is_database? )
|
74
|
+
|
75
|
+
|
55
76
|
### Return a Time object from the given +milliseconds+ epoch time.
|
56
77
|
def self::timestamp_from_timestamp_ms( milliseconds )
|
57
78
|
seconds, subsec = milliseconds.divmod( 1_000 )
|
@@ -74,4 +95,3 @@ module Kuzu
|
|
74
95
|
end
|
75
96
|
|
76
97
|
end # module Kuzu
|
77
|
-
|
data/spec/kuzu/database_spec.rb
CHANGED
@@ -8,12 +8,18 @@ require 'kuzu/database'
|
|
8
8
|
RSpec.describe( Kuzu::Database ) do
|
9
9
|
|
10
10
|
let( :spec_tmpdir ) do
|
11
|
-
tmpfile_pathname()
|
11
|
+
path = tmpfile_pathname()
|
12
|
+
path.mkpath
|
13
|
+
return path
|
12
14
|
end
|
13
15
|
|
14
16
|
let( :db_path ) { spec_tmpdir + 'spec_db' }
|
15
17
|
|
16
18
|
|
19
|
+
after( :each ) do
|
20
|
+
GC.start
|
21
|
+
end
|
22
|
+
|
17
23
|
it "can be created in-memory" do
|
18
24
|
instance = described_class.new( '' )
|
19
25
|
expect( instance ).to be_a( described_class )
|
data/spec/kuzu/result_spec.rb
CHANGED
@@ -89,6 +89,22 @@ RSpec.describe( Kuzu::Result ) do
|
|
89
89
|
end
|
90
90
|
|
91
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
|
+
|
92
108
|
it "can fetch individual result tuples via the index operator" do
|
93
109
|
setup_demo_db()
|
94
110
|
|
data/spec/kuzu/types_spec.rb
CHANGED
@@ -34,6 +34,25 @@ RSpec.describe( "data types" ) do
|
|
34
34
|
end
|
35
35
|
|
36
36
|
|
37
|
+
it "coverts DATE values to Date objects" do
|
38
|
+
result = connection.query( %{RETURN CAST('2025-07-14', 'DATE') as x;} )
|
39
|
+
|
40
|
+
expect( result ).to be_a( Kuzu::Result )
|
41
|
+
expect( result ).to be_success
|
42
|
+
|
43
|
+
value = result.first
|
44
|
+
expect( value ).to include( 'x' )
|
45
|
+
|
46
|
+
x = value['x']
|
47
|
+
expect( x ).to be_a( Date )
|
48
|
+
expect( x.year ).to eq( 2025 )
|
49
|
+
expect( x.month ).to eq( 7 )
|
50
|
+
expect( x.day ).to eq( 14 )
|
51
|
+
|
52
|
+
result.finish
|
53
|
+
end
|
54
|
+
|
55
|
+
|
37
56
|
it "converts STRUCT values to OpenStructs" do
|
38
57
|
result = connection.query( "RETURN {first: 'Adam', last: 'Smith'} AS record;" )
|
39
58
|
|
@@ -251,4 +270,15 @@ RSpec.describe( "data types" ) do
|
|
251
270
|
end
|
252
271
|
|
253
272
|
|
273
|
+
it "converts SERIAL types to Integer objects" do
|
274
|
+
result = connection.query( %{RETURN CAST(133, "SERIAL") AS s;} )
|
275
|
+
rval = result.first['s']
|
276
|
+
|
277
|
+
expect( rval ).to be_an( Integer )
|
278
|
+
expect( rval ).to eq( 133 )
|
279
|
+
|
280
|
+
result.finish
|
281
|
+
end
|
282
|
+
|
283
|
+
|
254
284
|
end
|
data/spec/kuzu_spec.rb
CHANGED
@@ -7,7 +7,9 @@ require 'kuzu'
|
|
7
7
|
RSpec.describe( Kuzu ) do
|
8
8
|
|
9
9
|
let( :spec_tmpdir ) do
|
10
|
-
tmpfile_pathname()
|
10
|
+
path = tmpfile_pathname()
|
11
|
+
path.mkpath
|
12
|
+
return path
|
11
13
|
end
|
12
14
|
|
13
15
|
|
@@ -49,7 +51,33 @@ RSpec.describe( Kuzu ) do
|
|
49
51
|
result = described_class.database( filename )
|
50
52
|
|
51
53
|
expect( result ).to be_a( Kuzu::Database )
|
52
|
-
|
54
|
+
if Kuzu.storage_version <= 38
|
55
|
+
expect( filename ).to be_a_directory
|
56
|
+
else
|
57
|
+
expect( filename ).to be_a_file
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
it "can tell whether a string looks like a path to a Kuzu database" do
|
63
|
+
path = spec_tmpdir + 'spec_db'
|
64
|
+
|
65
|
+
expect {
|
66
|
+
described_class.database( path )
|
67
|
+
}.to change {
|
68
|
+
described_class.is_database?( path.to_s )
|
69
|
+
}.from( false ).to( true )
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
it "can tell whether a Pathname looks like a path to a Kuzu database" do
|
74
|
+
path = spec_tmpdir + 'spec_db'
|
75
|
+
|
76
|
+
expect {
|
77
|
+
described_class.database( path )
|
78
|
+
}.to change {
|
79
|
+
described_class.is_database?( path )
|
80
|
+
}.from( false ).to( true )
|
53
81
|
end
|
54
82
|
|
55
83
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -54,7 +54,7 @@ module Kuzu::SpecHelpers
|
|
54
54
|
|
55
55
|
### Return a Pathname pointing to a temporary file.
|
56
56
|
def tmpfile_pathname( filetype='spec' )
|
57
|
-
Pathname(Dir::Tmpname.create(['kuzu-', '-test-' + filetype]) {})
|
57
|
+
return Pathname( Dir::Tmpname.create(['kuzu-', '-test-' + filetype]) {} )
|
58
58
|
end
|
59
59
|
|
60
60
|
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-kuzu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Granger
|
@@ -34,7 +34,7 @@ cert_chain:
|
|
34
34
|
8qAqdfV+4u6Huu1KzAuDQCheyEyISsLST37sU/irV3czV6BiFipWag1XiJciRT3A
|
35
35
|
wZqCfTNVHTdtsCbfdA1DsA3RdG2iEH3TOHzv1Rqzqh4=
|
36
36
|
-----END CERTIFICATE-----
|
37
|
-
date: 2025-
|
37
|
+
date: 2025-07-16 00:00:00.000000000 Z
|
38
38
|
dependencies:
|
39
39
|
- !ruby/object:Gem::Dependency
|
40
40
|
name: rake-compiler
|
@@ -158,7 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
158
158
|
- !ruby/object:Gem::Version
|
159
159
|
version: '0'
|
160
160
|
requirements: []
|
161
|
-
rubygems_version: 3.6.
|
161
|
+
rubygems_version: 3.6.9
|
162
162
|
specification_version: 4
|
163
163
|
summary: A Ruby binding for the Kùzu embedded graph database.
|
164
164
|
test_files: []
|
metadata.gz.sig
CHANGED
@@ -1,2 +1,3 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
J�DZ#\A3��K���&��E����Q���dEcߋM� 9��8S�����Y��"_���'����N����A'8S�V 8��(�u��y�}bO��ҹG�ņ���C˽h؇�4��S� \�.�)9
|
2
|
+
���{��~86�#+�tu����R�����_�}x��<gx8�;)a����U��%��
|
3
|
+
|��r�7˫�<}nVt�ml�SN�%Ѳ��4������j��F���n^M���������ZG��0N��D�����N���8�ϔU��u�����P�g�q��qf�h�C��c�?2�A�owe�#�.
|