ruby-kuzu 0.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,51 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'loggability'
4
+
5
+ require 'kuzu' unless defined?( Kuzu )
6
+
7
+
8
+ # Kùzu connection class
9
+ class Kuzu::Connection
10
+ extend Loggability
11
+
12
+
13
+ # Loggability API -- log to Kuzu's logger
14
+ log_to :kuzu
15
+
16
+
17
+ ### Execute the given +query_string+ via the connection and return the
18
+ ### Kuzu::Result. If a block is given, the result will instead be yielded to it,
19
+ ### finished when it returns, and the return value of the block will be returned
20
+ ### instead.
21
+ def query( query_string, &block )
22
+ result = self._query( query_string )
23
+ return Kuzu::Result.wrap_block_result( result, &block )
24
+ end
25
+
26
+
27
+ ### Create a new Kuzu::PreparedStatement for the specified +query_string+.
28
+ def prepare( query_string )
29
+ return Kuzu::PreparedStatement.new( self, query_string )
30
+ end
31
+
32
+
33
+ ### Executes the given +statement+ (a Kuzu::PreparedStatement) after binding
34
+ ### the given +bound_variables+ to it.
35
+ def execute( statement, **bound_variables, &block )
36
+ statement.bind( **bound_variables )
37
+ return statement.execute
38
+ end
39
+
40
+
41
+ ### Return a string representation of the receiver suitable for debugging.
42
+ def inspect
43
+ details = " threads:%d" % [
44
+ self.max_num_threads_for_exec,
45
+ ]
46
+
47
+ default = super
48
+ return default.sub( />/, details + '>' )
49
+ end
50
+
51
+ end # class Kuzu::Connection
@@ -0,0 +1,53 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'loggability'
4
+
5
+ require 'kuzu' unless defined?( Kuzu )
6
+
7
+
8
+ # Main Kùzu database class
9
+ class Kuzu::Database
10
+ extend Loggability
11
+
12
+
13
+ # Loggability API -- log to Kuzu's logger
14
+ log_to :kuzu
15
+
16
+
17
+ ### Return a connection to this database.
18
+ def connect
19
+ return Kuzu::Connection.new( self )
20
+ end
21
+
22
+
23
+ ### Return +true+ if this database was created in read-only mode.
24
+ def read_only?
25
+ return self.config.read_only
26
+ end
27
+
28
+
29
+ ### Returns +true+ if this database will automatically checkpoint when the size of
30
+ ### the WAL file exceeds the `checkpoint_threshold`.
31
+ def auto_checkpointing?
32
+ return self.config.auto_checkpoint
33
+ end
34
+
35
+
36
+ ### Returns +true+ if this database uses compression for data on disk.
37
+ def compression_enabled?
38
+ return self.config.enable_compression
39
+ end
40
+
41
+
42
+ ### Return a string representation of the receiver suitable for debugging.
43
+ def inspect
44
+ details = " path:%p read-only:%p" % [
45
+ self.path,
46
+ self.read_only?,
47
+ ]
48
+
49
+ default = super
50
+ return default.sub( />/, details + '>' )
51
+ end
52
+
53
+ end # class Kuzu::Database
data/lib/kuzu/node.rb ADDED
@@ -0,0 +1,46 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'forwardable'
4
+ require 'loggability'
5
+
6
+ require 'kuzu' unless defined?( Kuzu )
7
+
8
+
9
+ # Kuzu node class
10
+ class Kuzu::Node
11
+ extend Loggability,
12
+ Forwardable
13
+
14
+ # Loggability API -- log to Kuzu's logger
15
+ log_to :kuzu
16
+
17
+
18
+ ### Create a new Node with the given +id+, +label+, and +properties+.
19
+ def initialize( id, label, **properties )
20
+ @id = id
21
+ @label = label
22
+ @properties = properties
23
+ end
24
+
25
+
26
+ ######
27
+ public
28
+ ######
29
+
30
+ ##
31
+ # The internal id value of the given node
32
+ attr_reader :id
33
+
34
+ ##
35
+ # The label value of the given node
36
+ attr_reader :label
37
+
38
+ ##
39
+ # The Hash of the Node's properties, keyed by name as a Symbol
40
+ attr_reader :properties
41
+
42
+
43
+ # Allow direct access to properties
44
+ def_delegators :@properties, :[], :[]=, :dig
45
+
46
+ end # class Kuzu::Node
@@ -0,0 +1,42 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'loggability'
4
+
5
+ require 'kuzu' unless defined?( Kuzu )
6
+
7
+
8
+ # A parameterized query which can avoid planning the same query for repeated execution
9
+ class Kuzu::PreparedStatement
10
+ extend Loggability
11
+
12
+ # Loggability API -- Use Kuzu's logger
13
+ log_to :kuzu
14
+
15
+
16
+ ### Execute the statement against its connection and return a Kuzu::Result.
17
+ ### If a +block+ is supplied, the result will be passed to it instead,
18
+ ### then finished automatically, and the return value of the block returned
19
+ ### instead.
20
+ def execute( **bound_variables, &block )
21
+ self.bind( **bound_variables )
22
+ result = self._execute
23
+ return Kuzu::Result.wrap_block_result( result, &block )
24
+ end
25
+
26
+
27
+ ### Execute the statement against its connection and return `true` if it
28
+ ### succeeded.
29
+ def execute!( **bound_variables )
30
+ self.bind( **bound_variables )
31
+ return self._execute!
32
+ end
33
+
34
+
35
+ ### Bind the variables in the specified +variable_map+ to the statement.
36
+ def bind( **variable_map )
37
+ variable_map.each do |name, value|
38
+ self.bind_variable( name, value )
39
+ end
40
+ end
41
+
42
+ end # class Kuzu::PreparedStatement
@@ -0,0 +1,28 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'loggability'
4
+
5
+ require 'kuzu' unless defined?( Kuzu )
6
+
7
+
8
+ # Kùzu query summary class
9
+ class Kuzu::QuerySummary
10
+ extend Loggability
11
+
12
+
13
+ # Loggability API -- log to Kuzu's logger
14
+ log_to :kuzu
15
+
16
+
17
+ ### Return a string representation of the receiver suitable for debugging.
18
+ def inspect
19
+ details = " compiling: %0.3fs execution: %0.3fs" % [
20
+ self.compiling_time,
21
+ self.execution_time,
22
+ ]
23
+
24
+ default = super
25
+ return default.sub( />/, details + '>' )
26
+ end
27
+
28
+ end # class Kuzu::QuerySummary
@@ -0,0 +1,37 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'forwardable'
4
+ require 'loggability'
5
+
6
+ require 'kuzu' unless defined?( Kuzu )
7
+
8
+
9
+ # Kuzu recursive relationship class
10
+ class Kuzu::RecursiveRel
11
+ extend Loggability,
12
+ Forwardable
13
+
14
+ # Loggability API -- log to Kuzu's logger
15
+ log_to :kuzu
16
+
17
+
18
+ ### Create a new RecursiveRel with the given +nodes+ and +rels+.
19
+ def initialize( nodes, rels )
20
+ @nodes = nodes
21
+ @rels = rels
22
+ end
23
+
24
+
25
+ ######
26
+ public
27
+ ######
28
+
29
+ ##
30
+ # The Array of Kuzu::Nodes in the chain
31
+ attr_reader :nodes
32
+
33
+ ##
34
+ # The Array of Kuzu::Rels connecting the Nodes in the chain
35
+ attr_reader :rels
36
+
37
+ end # class Kuzu::RecursiveRel
data/lib/kuzu/rel.rb ADDED
@@ -0,0 +1,51 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'forwardable'
4
+ require 'loggability'
5
+
6
+ require 'kuzu' unless defined?( Kuzu )
7
+
8
+
9
+ # Kuzu rel (relationship) class
10
+ class Kuzu::Rel
11
+ extend Loggability,
12
+ Forwardable
13
+
14
+ # Loggability API -- log to Kuzu's logger
15
+ log_to :kuzu
16
+
17
+
18
+ ### Create a new Rel with the given +id+, +label+, and +properties+.
19
+ def initialize( src_id, dst_id, label, **properties )
20
+ @src_id = src_id
21
+ @dst_id = dst_id
22
+ @label = label
23
+ @properties = properties
24
+ end
25
+
26
+
27
+ ######
28
+ public
29
+ ######
30
+
31
+ ##
32
+ # The internal id value of the source node
33
+ attr_reader :src_id
34
+
35
+ ##
36
+ # The internal id value of the destination node
37
+ attr_reader :dst_id
38
+
39
+ ##
40
+ # The label value of the given node
41
+ attr_reader :label
42
+
43
+ ##
44
+ # The Hash of the Rel's properties, keyed by name as a Symbol
45
+ attr_reader :properties
46
+
47
+
48
+ # Allow direct access to properties
49
+ def_delegators :@properties, :[], :[]=, :dig
50
+
51
+ end # class Kuzu::Rel
@@ -0,0 +1,139 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'loggability'
4
+
5
+ require 'kuzu' unless defined?( Kuzu )
6
+
7
+
8
+ # Kùzu query result class
9
+ class Kuzu::Result
10
+ extend Loggability
11
+
12
+
13
+ # Loggability API -- log to Kuzu's logger
14
+ log_to :kuzu
15
+
16
+
17
+ ### Execute the given +query+ via the specified +connection+ and return the
18
+ ### Kuzu::Result. If a block is given, the result will instead be yielded to it,
19
+ ### finished when it returns, and the return value of the block will be returned
20
+ ### instead.
21
+ def self::from_query( connection, query, &block )
22
+ return connection.query( query, &block )
23
+ end
24
+
25
+
26
+ ### Execute the given +statement+ and return the Kuzu::Result. If a block is given,
27
+ ### the result will instead be yielded to it, finished when it returns, and the
28
+ ### return value of the block will be returned instead.
29
+ def self::from_prepared_statement( statement, &block )
30
+ return statement.execute( &block )
31
+ end
32
+
33
+
34
+ ### If the +block+ is provided, yield +result+ to it and then call #finish on it,
35
+ ### returning the +block+ result. If +block+ is not given, just return +result+.
36
+ def self::wrap_block_result( result, &block )
37
+ return result unless block
38
+
39
+ begin
40
+ rval = block.call( result )
41
+ ensure
42
+ result.finish
43
+ end
44
+
45
+ return rval
46
+ end
47
+
48
+
49
+ ### Fetch the names of the columns in the result as an Array of Strings.
50
+ def column_names
51
+ return @column_names ||= self.get_column_names
52
+ end
53
+
54
+
55
+ ### Return a Kuzu::QuerySummary for the query that generated the Result.
56
+ def query_summary
57
+ return Kuzu::QuerySummary.from_result( self )
58
+ end
59
+
60
+
61
+ ### Get the next tuple of the result as a Hash.
62
+ def next
63
+ pairs = self.column_names.zip( self.get_next_values )
64
+ return Hash[ pairs ]
65
+ end
66
+
67
+
68
+ ### Iterate over each tuple of the result, yielding it to the +block+. If no
69
+ ### +block+ is given, return an Enumerator that will yield them instead.
70
+ def each( &block )
71
+ enum = self.tuple_enum
72
+ return enum.each( &block ) if block
73
+ return enum
74
+ end
75
+
76
+
77
+ ### Return the next result set after this one as a Kuzu::Result, or `nil`if
78
+ ### there is no next set.
79
+ def next_set
80
+ return nil unless self.has_next_set?
81
+ return self.class.from_next_set( self )
82
+ end
83
+
84
+
85
+ ### Iterate over each result set in the results, yielding it to the block. If
86
+ ### no +block+ is given, return an Enumerator tht will yield each set as its own
87
+ ### Result.
88
+ def each_set( &block )
89
+ enum = self.next_set_enum
90
+ return enum.each( &block ) if block
91
+ return enum
92
+ end
93
+
94
+
95
+ ### Return a string representation of the receiver suitable for debugging.
96
+ def inspect
97
+ if self.finished?
98
+ details = " (finished)"
99
+ else
100
+ details = " success: %p (%d tuples of %d columns): %s" % [
101
+ self.success?,
102
+ self.num_tuples,
103
+ self.num_columns,
104
+ self.to_s,
105
+ ]
106
+ end
107
+
108
+ default = super
109
+ return default.sub( />/, details + '>' )
110
+ end
111
+
112
+
113
+ #########
114
+ protected
115
+ #########
116
+
117
+ ### Return an Enumerator that yields result tuples as Hashes.
118
+ def tuple_enum
119
+ return Enumerator.new do |yielder|
120
+ self.reset_iterator
121
+ while self.has_next?
122
+ tuple = self.next
123
+ yielder.yield( tuple )
124
+ end
125
+ end
126
+ end
127
+
128
+
129
+ def next_set_enum
130
+ result = self
131
+ return Enumerator.new do |yielder|
132
+ while result
133
+ yielder.yield( result )
134
+ result = result.next_set
135
+ end
136
+ end
137
+ end
138
+
139
+ end # class Kuzu::Result
data/lib/kuzu.rb ADDED
@@ -0,0 +1,79 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'loggability'
4
+
5
+ require_relative 'kuzu_ext'
6
+
7
+ #--
8
+ # See also: ext/kuzu_ext/kuzu_ext.c
9
+ module Kuzu
10
+ extend Loggability
11
+
12
+
13
+ # Library version
14
+ VERSION = '0.0.1'
15
+
16
+
17
+ # Set up a logger for Kuzu classes
18
+ log_as :kuzu
19
+
20
+
21
+ ### Create and return a Kuzu::Database. If +path+ is +nil+, an empty string, or
22
+ ### the Symbol :memory, creates an in-memory database. Valid options are:
23
+ ###
24
+ ### `:buffer_pool_size`
25
+ ### : Max size of the buffer pool in bytes.
26
+ ###
27
+ ### `:max_num_threads`
28
+ ### : The maximum number of threads to use during query execution.
29
+ ###
30
+ ### `:enable_compression`
31
+ ### : Whether or not to compress data on-disk for supported types
32
+ ###
33
+ ### `:read_only`
34
+ ### : If true, open the database in read-only mode. No write transaction is allowed on the
35
+ ### Database object. If false, open the database read-write.
36
+ ###
37
+ ### `:max_db_size`
38
+ ### : The maximum size of the database in bytes.
39
+ ###
40
+ ### `:auto_checkpoint`
41
+ ### : If true, the database will automatically checkpoint when the size of
42
+ ### the WAL file exceeds the checkpoint threshold.
43
+ ###
44
+ ### `:checkpoint_threshold`
45
+ ### : The threshold of the WAL file size in bytes. When the size of the
46
+ ### WAL file exceeds this threshold, the database will checkpoint if
47
+ ### `auto_checkpoint` is true.
48
+ def self::database( path='', **config )
49
+ path = '' if path.nil? || path == :memory
50
+ self.log.info "Opening database %p" % [ path ]
51
+ return Kuzu::Database.new( path.to_s, **config )
52
+ end
53
+
54
+
55
+ ### Return a Time object from the given +milliseconds+ epoch time.
56
+ def self::timestamp_from_timestamp_ms( milliseconds )
57
+ seconds, subsec = milliseconds.divmod( 1_000 )
58
+ return Time.at( seconds, subsec, :millisecond )
59
+ end
60
+
61
+
62
+ ### Return a Time object from the given +microseconds+ epoch time and
63
+ ### optional timezone offset in seconds via the +zone+ argument.
64
+ def self::timestamp_from_timestamp_us( microseconds, zone=nil )
65
+ seconds, subsec = microseconds.divmod( 1_000_000 )
66
+ return Time.at( seconds, subsec, :microsecond, in: zone )
67
+ end
68
+
69
+
70
+ ### Return a Time object from the given +nanoseconds+ epoch time.
71
+ def self::timestamp_from_timestamp_ns( nanoseconds )
72
+ seconds, subsec = nanoseconds.divmod( 1_000_000_000 )
73
+ return Time.at( seconds, subsec, :nanosecond )
74
+ end
75
+
76
+ end # module Kuzu
77
+
78
+ # Fancy alias
79
+ Kùzu = Kuzu
@@ -0,0 +1,98 @@
1
+ # -*- ruby -*-
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ require 'kuzu/config'
6
+
7
+
8
+ RSpec.describe( Kuzu::Config ) do
9
+
10
+ it "can be created with defaults" do
11
+ result = described_class.new
12
+
13
+ expect( result ).to be_a( described_class )
14
+
15
+ expect( result.buffer_pool_size ).to be_a( Integer )
16
+ expect( result.max_num_threads ).to be_a( Integer )
17
+ expect( result.enable_compression ).to eq( true )
18
+ expect( result.read_only ).to eq( false )
19
+ expect( result.max_db_size ).to be_a( Integer )
20
+ expect( result.auto_checkpoint ).to eq( true )
21
+ expect( result.checkpoint_threshold ).to be_a( Integer )
22
+ end
23
+
24
+
25
+ it "can set its buffer_pool_size" do
26
+ instance = described_class.new
27
+
28
+ expect {
29
+ instance.buffer_pool_size = 2 ** 11
30
+ }.to change { instance.buffer_pool_size }.to( 2 ** 11 )
31
+ end
32
+
33
+
34
+ it "can set its max_num_threads" do
35
+ instance = described_class.new
36
+
37
+ expect {
38
+ instance.max_num_threads = 4
39
+ }.to change { instance.max_num_threads }.to( 4 )
40
+ end
41
+
42
+
43
+ it "can disable compression" do
44
+ instance = described_class.new
45
+
46
+ expect {
47
+ instance.enable_compression = false
48
+ }.to change { instance.enable_compression }.to( false )
49
+ end
50
+
51
+
52
+ it "can set read-only mode" do
53
+ instance = described_class.new
54
+
55
+ expect {
56
+ instance.read_only = true
57
+ }.to change { instance.read_only }.to( true )
58
+ end
59
+
60
+
61
+ it "can set read-only mode using a truthy value" do
62
+ instance = described_class.new
63
+
64
+ expect {
65
+ instance.read_only = :yep
66
+ }.to change { instance.read_only }.to( true )
67
+ end
68
+
69
+
70
+ it "can set its max_db_size" do
71
+ instance = described_class.new
72
+
73
+ expect {
74
+ instance.max_db_size = 2 ** 21
75
+ }.to change { instance.max_db_size }.to( 2 ** 21 )
76
+ end
77
+
78
+
79
+ it "can disable auto-checkpointing" do
80
+ instance = described_class.new
81
+
82
+ expect {
83
+ instance.auto_checkpoint = false
84
+ }.to change { instance.auto_checkpoint }.to( false )
85
+ end
86
+
87
+
88
+ it "can set its checkpoint threshold" do
89
+ instance = described_class.new
90
+
91
+ expect {
92
+ instance.checkpoint_threshold = 2 ** 22
93
+ }.to change { instance.checkpoint_threshold }.to( 2 ** 22 )
94
+ end
95
+
96
+
97
+ end
98
+
@@ -0,0 +1,51 @@
1
+ # -*- ruby -*-
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ require 'kuzu/database'
6
+
7
+
8
+ RSpec.describe( Kuzu::Database ) do
9
+
10
+ let( :spec_tmpdir ) do
11
+ tmpfile_pathname()
12
+ end
13
+
14
+ let( :db_path ) { spec_tmpdir + 'spec_db' }
15
+
16
+
17
+ it "can be created in-memory" do
18
+ instance = described_class.new( '' )
19
+ expect( instance ).to be_a( described_class )
20
+ end
21
+
22
+
23
+ it "can be created read-only from an existing on-disk database" do
24
+ original = described_class.new( db_path.to_s )
25
+
26
+ ro = described_class.new( db_path.to_s, read_only: true )
27
+ expect( ro ).to be_read_only
28
+ end
29
+
30
+
31
+ it "can be created without auto-checkpointing" do
32
+ instance = described_class.new( db_path.to_s, auto_checkpoint: false )
33
+ expect( instance ).not_to be_auto_checkpointing
34
+ end
35
+
36
+
37
+ it "can be created without compression" do
38
+ instance = described_class.new( db_path.to_s, enable_compression: false )
39
+ expect( instance ).not_to be_compression_enabled
40
+ end
41
+
42
+
43
+ it "can create a connection to itself" do
44
+ instance = described_class.new( db_path.to_s, enable_compression: false )
45
+
46
+ result = instance.connect
47
+
48
+ expect( result ).to be_a( Kuzu::Connection )
49
+ end
50
+
51
+ end