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,310 @@
1
+ /*
2
+ * connection.c - Kuzu::Connection class
3
+ *
4
+ */
5
+
6
+ #include "kuzu.h"
7
+ #include "kuzu_ext.h"
8
+ #include "ruby/thread.h"
9
+
10
+ #define CHECK_CONNECTION(self) ((rkuzu_connection *)rb_check_typeddata((self), &rkuzu_connection_type))
11
+ // #define DEBUG_GC(msg, ptr) fprintf( stderr, msg, ptr )
12
+ #define DEBUG_GC(msg, ptr)
13
+
14
+
15
+ VALUE rkuzu_cKuzuConnection;
16
+
17
+ static void rkuzu_connection_free( void * );
18
+ static void rkuzu_connection_mark( void * );
19
+
20
+ static const rb_data_type_t rkuzu_connection_type = {
21
+ .wrap_struct_name = "Kuzu::Connection",
22
+ .function = {
23
+ .dfree = rkuzu_connection_free,
24
+ .dmark = rkuzu_connection_mark,
25
+ },
26
+ .data = NULL,
27
+ };
28
+
29
+
30
+ rkuzu_connection *
31
+ rkuzu_get_connection( VALUE conn_obj )
32
+ {
33
+ return CHECK_CONNECTION( conn_obj );
34
+ }
35
+
36
+
37
+ static rkuzu_connection *
38
+ rkuzu_connection_alloc( void )
39
+ {
40
+ rkuzu_connection *ptr = ALLOC( rkuzu_connection );
41
+
42
+ ptr->database = Qnil;
43
+ ptr->queries = rb_ary_new();
44
+ ptr->statements = rb_ary_new();
45
+
46
+ return ptr;
47
+ }
48
+
49
+
50
+ static void
51
+ rkuzu_connection_free( void *ptr )
52
+ {
53
+ rkuzu_connection *conn_s = (rkuzu_connection *)ptr;
54
+
55
+ if ( ptr ) {
56
+ DEBUG_GC( ">>> freeing connection %p\n", ptr );
57
+
58
+ kuzu_connection_destroy( &conn_s->conn );
59
+
60
+ xfree( ptr );
61
+ ptr = NULL;
62
+ }
63
+ }
64
+
65
+
66
+ static void
67
+ rkuzu_connection_mark( void *ptr )
68
+ {
69
+ rkuzu_connection *conn_s = (rkuzu_connection *)ptr;
70
+
71
+ if ( ptr ) {
72
+ DEBUG_GC( ">>> marking connection %p\n", ptr );
73
+ rb_gc_mark( conn_s->database );
74
+ rb_gc_mark( conn_s->statements );
75
+ rb_gc_mark( conn_s->queries );
76
+ }
77
+ }
78
+
79
+
80
+ /*
81
+ * ::allocate function
82
+ */
83
+ static VALUE
84
+ rkuzu_connection_s_allocate( VALUE klass )
85
+ {
86
+ return TypedData_Wrap_Struct( klass, &rkuzu_connection_type, NULL );
87
+ }
88
+
89
+
90
+
91
+ /*
92
+ * call-seq:
93
+ * new( database ) -> connection
94
+ *
95
+ * Create a Kuzu::Connection to the specified +database+.
96
+ *
97
+ */
98
+ static VALUE
99
+ rkuzu_connection_initialize( VALUE self, VALUE database )
100
+ {
101
+ rkuzu_connection *ptr = CHECK_CONNECTION( self );
102
+
103
+ if ( !ptr ) {
104
+ rkuzu_database *dbobject = rkuzu_get_database( database );
105
+ ptr = rkuzu_connection_alloc();
106
+
107
+ if ( kuzu_connection_init(&dbobject->db, &ptr->conn) != KuzuSuccess ) {
108
+ xfree( ptr );
109
+ ptr = NULL;
110
+ rb_raise( rkuzu_eConnectionError, "Failed to connect!" );
111
+ }
112
+
113
+ DEBUG_GC( ">>> allocated connection %p\n", ptr );
114
+ RTYPEDDATA_DATA( self ) = ptr;
115
+
116
+ ptr->database = database;
117
+
118
+ } else {
119
+ rb_raise( rb_eRuntimeError, "cannot reinit connection" );
120
+ }
121
+
122
+ rb_call_super( 0, 0 );
123
+
124
+ return Qtrue;
125
+ }
126
+
127
+
128
+ struct query_call {
129
+ kuzu_connection *conn;
130
+ const char *query_s;
131
+ kuzu_query_result *result;
132
+ };
133
+
134
+
135
+ static void *
136
+ rkuzu_connection_do_query_without_gvl( void *ptr )
137
+ {
138
+ struct query_call *qcall = (struct query_call *)ptr;
139
+ kuzu_state state;
140
+
141
+ state = kuzu_connection_query( qcall->conn, qcall->query_s, qcall->result );
142
+
143
+ return (void *)state;
144
+ }
145
+
146
+
147
+ static void
148
+ rkuzu_connection_cancel_query( void *ptr )
149
+ {
150
+ kuzu_connection *conn = (kuzu_connection *)ptr;
151
+ kuzu_connection_interrupt( conn );
152
+ }
153
+
154
+
155
+ static kuzu_query_result
156
+ rkuzu_connection_do_query( VALUE self, VALUE query )
157
+ {
158
+ rkuzu_connection *conn = CHECK_CONNECTION( self );
159
+ const char *query_s = StringValueCStr( query );
160
+ kuzu_query_result result;
161
+ kuzu_state query_state;
162
+ struct query_call qcall;
163
+ void *result_ptr;
164
+
165
+ qcall.conn = &conn->conn;
166
+ qcall.query_s = query_s;
167
+ qcall.result = &result;
168
+
169
+ result_ptr = rb_thread_call_without_gvl(
170
+ rkuzu_connection_do_query_without_gvl, (void *)&qcall,
171
+ rkuzu_connection_cancel_query, (void *)&conn->conn );
172
+
173
+ query_state = (kuzu_state)result_ptr;
174
+
175
+ if ( query_state != KuzuSuccess ) {
176
+ char *err_detail = kuzu_query_result_get_error_message( &result );
177
+ char errmsg[ 4096 ] = "\0";
178
+
179
+ snprintf( errmsg, 4096, "Could not execute query `%s': %s.", query_s, err_detail );
180
+
181
+ kuzu_destroy_string( err_detail );
182
+ kuzu_query_result_destroy( &result );
183
+
184
+ rb_raise( rkuzu_eQueryError, "%s", errmsg );
185
+ }
186
+
187
+ return *qcall.result;
188
+ }
189
+
190
+
191
+
192
+ static VALUE
193
+ rkuzu_connection__query( VALUE self, VALUE query )
194
+ {
195
+ kuzu_query_result result = rkuzu_connection_do_query( self, query );
196
+ return rkuzu_result_from_query( rkuzu_cKuzuResult, self, query, result );
197
+ }
198
+
199
+
200
+ /*
201
+ * call-seq:
202
+ * connection.query!( query_string )
203
+ *
204
+ * Execute the given +query_string+ and return `true` if the query was
205
+ * successful.
206
+ *
207
+ */
208
+ static VALUE
209
+ rkuzu_connection_query_bang( VALUE self, VALUE query )
210
+ {
211
+ kuzu_query_result result = rkuzu_connection_do_query( self, query );
212
+ return kuzu_query_result_is_success( &result ) ? Qtrue : Qfalse;
213
+ }
214
+
215
+
216
+ /*
217
+ * call-seq:
218
+ * connection.max_num_threads_for_exec -> integer
219
+ *
220
+ * Returns the maximum number of threads of the connection to use for
221
+ * executing queries.
222
+ *
223
+ */
224
+ static VALUE
225
+ rkuzu_connection_max_num_threads_for_exec( VALUE self )
226
+ {
227
+ rkuzu_connection *ptr = CHECK_CONNECTION( self );
228
+ uint64_t count;
229
+
230
+ if ( kuzu_connection_get_max_num_thread_for_exec( &ptr->conn, &count ) != KuzuSuccess ) {
231
+ rb_raise( rkuzu_eError, "kuzu_connection_get_max_num_thread_for_exec failed" );
232
+ }
233
+
234
+ return ULONG2NUM( count );
235
+ }
236
+
237
+
238
+ /*
239
+ * call-seq:
240
+ * connection.max_num_threads_for_exec = integer
241
+ *
242
+ * Sets the maximum number of threads of the connection to use for
243
+ * executing queries.
244
+ *
245
+ */
246
+ static VALUE
247
+ rkuzu_connection_max_num_threads_for_exec_eq( VALUE self, VALUE count )
248
+ {
249
+ rkuzu_connection *ptr = CHECK_CONNECTION( self );
250
+ uint64_t thread_count = NUM2ULONG( count );
251
+
252
+ if ( kuzu_connection_set_max_num_thread_for_exec( &ptr->conn, thread_count ) != KuzuSuccess ) {
253
+ rb_raise( rkuzu_eError, "kuzu_connection_set_max_num_thread_for_exec failed" );
254
+ }
255
+
256
+ return Qtrue;
257
+ }
258
+
259
+
260
+ /*
261
+ * call-seq:
262
+ * connection.query_timeout = integer
263
+ *
264
+ * Sets query timeout value in milliseconds for the connection.
265
+ *
266
+ */
267
+ static VALUE
268
+ rkuzu_connection_query_timeout_eq( VALUE self, VALUE timeout )
269
+ {
270
+ rkuzu_connection *ptr = CHECK_CONNECTION( self );
271
+ uint64_t timeout_in_ms = NUM2ULONG( timeout );
272
+
273
+ if ( kuzu_connection_set_query_timeout( &ptr->conn, timeout_in_ms ) != KuzuSuccess ) {
274
+ rb_raise( rkuzu_eError, "kuzu_connection_set_query_timeout failed" );
275
+ }
276
+
277
+ return Qtrue;
278
+ }
279
+
280
+
281
+
282
+ /*
283
+ * Document-class: Kuzu::Connection
284
+ */
285
+ void
286
+ rkuzu_init_connection( void )
287
+ {
288
+ #ifdef FOR_RDOC
289
+ rkuzu_mKuzu = rb_define_module( "Kuzu" );
290
+ #endif
291
+
292
+ rkuzu_cKuzuConnection = rb_define_class_under( rkuzu_mKuzu, "Connection", rb_cObject );
293
+
294
+ rb_define_alloc_func( rkuzu_cKuzuConnection, rkuzu_connection_s_allocate );
295
+
296
+ rb_define_protected_method( rkuzu_cKuzuConnection, "initialize", rkuzu_connection_initialize, 1 );
297
+
298
+ rb_define_protected_method( rkuzu_cKuzuConnection, "_query", rkuzu_connection__query, 1 );
299
+ rb_define_method( rkuzu_cKuzuConnection, "query!", rkuzu_connection_query_bang, 1 );
300
+ rb_define_alias( rkuzu_cKuzuConnection, "run", "query!" );
301
+
302
+ rb_define_method( rkuzu_cKuzuConnection, "max_num_threads_for_exec",
303
+ rkuzu_connection_max_num_threads_for_exec, 0 );
304
+ rb_define_method( rkuzu_cKuzuConnection, "max_num_threads_for_exec=",
305
+ rkuzu_connection_max_num_threads_for_exec_eq, 1 );
306
+
307
+ rb_define_method( rkuzu_cKuzuConnection, "query_timeout=", rkuzu_connection_query_timeout_eq, 1 );
308
+
309
+ rb_require( "kuzu/connection" );
310
+ }
@@ -0,0 +1,196 @@
1
+ /*
2
+ * database.c - Kuzu::Database class
3
+ */
4
+
5
+ #include "kuzu_ext.h"
6
+
7
+ #define CHECK_DATABASE(self) ((rkuzu_database*)rb_check_typeddata((self), &rkuzu_database_type))
8
+ // #define DEBUG_GC(msg, ptr) fprintf( stderr, msg, ptr )
9
+ #define DEBUG_GC(msg, ptr)
10
+
11
+
12
+ VALUE rkuzu_cKuzuDatabase;
13
+
14
+ static void rkuzu_database_free( void * );
15
+ static void rkuzu_database_mark( void * );
16
+
17
+ static const rb_data_type_t rkuzu_database_type = {
18
+ .wrap_struct_name = "Kuzu::Database",
19
+ .function = {
20
+ .dmark = rkuzu_database_mark,
21
+ .dfree = rkuzu_database_free,
22
+ },
23
+ .data = NULL,
24
+ };
25
+
26
+
27
+ rkuzu_database *
28
+ rkuzu_get_database( VALUE obj )
29
+ {
30
+ return CHECK_DATABASE( obj );
31
+ }
32
+
33
+
34
+ /*
35
+ * Struct allocation/init function.
36
+ */
37
+ static rkuzu_database *
38
+ rkuzu_database_alloc( void )
39
+ {
40
+ rkuzu_database *ptr = ALLOC( rkuzu_database );
41
+
42
+ ptr->path = Qnil;
43
+ ptr->config = Qnil;
44
+
45
+ return ptr;
46
+ }
47
+
48
+
49
+ /*
50
+ * Free function
51
+ */
52
+ static void
53
+ rkuzu_database_free( void *ptr )
54
+ {
55
+ if ( ptr ) {
56
+ DEBUG_GC( ">>> freeing database %p\n", ptr );
57
+ rkuzu_database *database_s = (rkuzu_database *)ptr;
58
+
59
+ kuzu_database_destroy( &database_s->db );
60
+
61
+ database_s->path = Qnil;
62
+ database_s->config = Qnil;
63
+
64
+ xfree( ptr );
65
+ ptr = NULL;
66
+ }
67
+ }
68
+
69
+
70
+ /*
71
+ * Mark function
72
+ */
73
+ static void
74
+ rkuzu_database_mark( void *ptr )
75
+ {
76
+ rkuzu_database *database_s = (rkuzu_database *)ptr;
77
+
78
+ DEBUG_GC( ">>> marking database %p\n", ptr );
79
+
80
+ rb_gc_mark( database_s->path );
81
+ rb_gc_mark( database_s->config );
82
+ }
83
+
84
+
85
+
86
+ /*
87
+ * ::allocate function
88
+ */
89
+ static VALUE
90
+ rkuzu_database_s_allocate( VALUE klass )
91
+ {
92
+ return TypedData_Wrap_Struct( klass, &rkuzu_database_type, NULL );
93
+ }
94
+
95
+
96
+ /*
97
+ * call-seq:
98
+ * database.new( path, **options ) -> database
99
+ *
100
+ * Create a new Datbase using the given +path+ and +options+.
101
+ *
102
+ */
103
+ static VALUE
104
+ rkuzu_database_initialize( int argc, VALUE *argv, VALUE self )
105
+ {
106
+ rkuzu_database *ptr = CHECK_DATABASE( self );
107
+
108
+ if ( !ptr ) {
109
+ VALUE path, options, config;
110
+ VALUE config_argv[1];
111
+ kuzu_system_config *sysconfig;
112
+ char *database_path;
113
+
114
+ rb_scan_args( argc, argv, "1:", &path, &options );
115
+ config_argv[0] = options;
116
+ config = rb_funcallv_public_kw( rkuzu_cKuzuConfig, rb_intern("from_options"), 1,
117
+ config_argv, RB_PASS_KEYWORDS );
118
+
119
+ sysconfig = rkuzu_get_config( config );
120
+ database_path = StringValueCStr( path );
121
+
122
+ ptr = rkuzu_database_alloc();
123
+ if ( kuzu_database_init(database_path, *sysconfig, &ptr->db) != KuzuSuccess ) {
124
+ xfree( ptr );
125
+ ptr = NULL;
126
+
127
+ rb_raise( rkuzu_eDatabaseError, "Couldn't create database!" );
128
+ }
129
+
130
+ DEBUG_GC( ">>> allocated database %p\n", ptr );
131
+ RTYPEDDATA_DATA( self ) = ptr;
132
+
133
+ ptr->path = rb_obj_freeze( rb_obj_dup(path) );
134
+ ptr->config = rb_obj_freeze( config );
135
+ } else {
136
+ rb_raise( rb_eRuntimeError, "cannot reinit database" );
137
+ }
138
+
139
+ rb_call_super( 0, 0 );
140
+
141
+ return Qtrue;
142
+ }
143
+
144
+
145
+ /*
146
+ * call-seq:
147
+ * database.config() -> config
148
+ *
149
+ * Return the Kuzu::Config that reflects the config options the database
150
+ * was created with.
151
+ *
152
+ */
153
+ static VALUE
154
+ rkuzu_database_config( VALUE self )
155
+ {
156
+ rkuzu_database *ptr = CHECK_DATABASE( self );
157
+ return ptr->config;
158
+ }
159
+
160
+
161
+
162
+ /*
163
+ * call-seq:
164
+ * database.path() -> string or nil
165
+ *
166
+ * Return the path the database was created with, or +nil+ if it was
167
+ * created in memory.
168
+ *
169
+ */
170
+ static VALUE
171
+ rkuzu_database_path( VALUE self )
172
+ {
173
+ rkuzu_database *ptr = CHECK_DATABASE( self );
174
+
175
+ if ( RSTRING_LEN(ptr->path) == 0 ) {
176
+ return Qnil;
177
+ } else {
178
+ return ptr->path;
179
+ }
180
+ }
181
+
182
+
183
+ void
184
+ rkuzu_init_database( void )
185
+ {
186
+ rkuzu_cKuzuDatabase = rb_define_class_under( rkuzu_mKuzu, "Database", rb_cObject );
187
+
188
+ rb_define_alloc_func( rkuzu_cKuzuDatabase, rkuzu_database_s_allocate );
189
+
190
+ rb_define_method( rkuzu_cKuzuDatabase, "initialize", rkuzu_database_initialize, -1 );
191
+
192
+ rb_define_method( rkuzu_cKuzuDatabase, "config", rkuzu_database_config, 0 );
193
+ rb_define_method( rkuzu_cKuzuDatabase, "path", rkuzu_database_path, 0 );
194
+
195
+ rb_require( "kuzu/database" );
196
+ }
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rbconfig'
4
+ require 'mkmf'
5
+
6
+ dir_config( 'libkuzu' )
7
+
8
+ have_library( 'kuzu' ) or
9
+ abort "No kuzu library!"
10
+
11
+ have_header( 'kuzu.h' ) or
12
+ abort "No kuzu.h header!"
13
+ have_header( 'ruby/thread.h' ) or
14
+ abort "Your Ruby is too old!"
15
+
16
+ have_func( 'kuzu_database_init', 'kuzu.h' )
17
+
18
+ create_header()
19
+ create_makefile( 'kuzu_ext' )
20
+
@@ -0,0 +1,158 @@
1
+ /*
2
+ * kuzu_ext.c - Ruby binding for Kùzu
3
+ *
4
+ * Authors:
5
+ * * Michael Granger <ged@FaerieMUD.org>
6
+ *
7
+ * Refs:
8
+ * - https://docs.kuzudb.com/
9
+ *
10
+ */
11
+
12
+ #include "kuzu_ext.h"
13
+
14
+ VALUE rkuzu_mKuzu;
15
+
16
+ VALUE rkuzu_eError;
17
+ VALUE rkuzu_eDatabaseError;
18
+ VALUE rkuzu_eConnectionError;
19
+ VALUE rkuzu_eQueryError;
20
+ VALUE rkuzu_eFinishedError;
21
+
22
+ VALUE rkuzu_rb_cDate;
23
+ VALUE rkuzu_rb_cOstruct;
24
+
25
+
26
+ /* --------------------------------------------------------------
27
+ * Logging Functions
28
+ * -------------------------------------------------------------- */
29
+
30
+ /*
31
+ * Log a message to the given +context+ object's logger.
32
+ */
33
+ void
34
+ #if HAVE_STDARG_PROTOTYPES
35
+ rkuzu_log_obj( VALUE context, const char *level, const char *fmt, ... )
36
+ #else
37
+ rkuzu_log_obj( VALUE context, const char *level, const char *fmt, va_dcl )
38
+ #endif
39
+ {
40
+ char buf[BUFSIZ];
41
+ va_list args;
42
+ VALUE logger = Qnil;
43
+ VALUE message = Qnil;
44
+
45
+ va_init_list( args, fmt );
46
+ vsnprintf( buf, BUFSIZ, fmt, args );
47
+ message = rb_str_new2( buf );
48
+
49
+ logger = rb_funcall( context, rb_intern("log"), 0 );
50
+ rb_funcall( logger, rb_intern(level), 1, message );
51
+
52
+ va_end( args );
53
+ }
54
+
55
+
56
+ /*
57
+ * Log a message to the global logger.
58
+ */
59
+ void
60
+ #if HAVE_STDARG_PROTOTYPES
61
+ rkuzu_log( const char *level, const char *fmt, ... )
62
+ #else
63
+ rkuzu_log( const char *level, const char *fmt, va_dcl )
64
+ #endif
65
+ {
66
+ char buf[BUFSIZ];
67
+ va_list args;
68
+ VALUE logger = Qnil;
69
+ VALUE message = Qnil;
70
+
71
+ va_init_list( args, fmt );
72
+ vsnprintf( buf, BUFSIZ, fmt, args );
73
+ message = rb_str_new2( buf );
74
+
75
+ logger = rb_funcall( rkuzu_mKuzu, rb_intern("logger"), 0 );
76
+ rb_funcall( logger, rb_intern(level), 1, message );
77
+
78
+ va_end( args );
79
+ }
80
+
81
+
82
+
83
+ /* --------------------------------------------------------------
84
+ * Module methods
85
+ * -------------------------------------------------------------- */
86
+
87
+ /*
88
+ * call-seq:
89
+ * Kuzu.kuzu_version -> string
90
+ *
91
+ * Return the version of the underlying Kuzu library.
92
+ *
93
+ */
94
+ static VALUE
95
+ rkuzu_s_kuzu_version( VALUE _ )
96
+ {
97
+ const char *version = kuzu_get_version();
98
+
99
+ return rb_str_new2( version );
100
+ }
101
+
102
+
103
+ /*
104
+ * call-seq:
105
+ * Kuzu.storage_version -> integer
106
+ *
107
+ * Return the storage version used by the underlying library.
108
+ *
109
+ */
110
+ static VALUE
111
+ rkuzu_s_storage_version( VALUE _ )
112
+ {
113
+ const unsigned long long version = kuzu_get_storage_version();
114
+
115
+ return ULONG2NUM( version );
116
+ }
117
+
118
+
119
+ /*
120
+ * Kuzu extension init function
121
+ */
122
+ void
123
+ Init_kuzu_ext( void )
124
+ {
125
+ rb_require( "date" );
126
+ rkuzu_rb_cDate = rb_const_get( rb_cObject, rb_intern("Date") );
127
+
128
+ rb_require( "ostruct" );
129
+ rkuzu_rb_cOstruct = rb_const_get( rb_cObject, rb_intern("OpenStruct") );
130
+
131
+ /*
132
+ * Document-module: Kuzu
133
+ *
134
+ * The top level namespace for Kuzu classes.
135
+ */
136
+ rkuzu_mKuzu = rb_define_module( "Kuzu" );
137
+
138
+ rb_define_singleton_method( rkuzu_mKuzu, "kuzu_version", rkuzu_s_kuzu_version, 0 );
139
+ rb_define_singleton_method( rkuzu_mKuzu, "storage_version", rkuzu_s_storage_version, 0 );
140
+
141
+ rkuzu_eError = rb_define_class_under( rkuzu_mKuzu, "Error", rb_eRuntimeError );
142
+ rkuzu_eDatabaseError = rb_define_class_under( rkuzu_mKuzu, "DatabaseError", rkuzu_eError );
143
+ rkuzu_eConnectionError = rb_define_class_under( rkuzu_mKuzu, "ConnectionError", rkuzu_eError );
144
+ rkuzu_eQueryError = rb_define_class_under( rkuzu_mKuzu, "QueryError", rkuzu_eError );
145
+ rkuzu_eFinishedError = rb_define_class_under( rkuzu_mKuzu, "FinishedError", rkuzu_eError );
146
+
147
+ rb_require( "kuzu" );
148
+
149
+ rkuzu_init_database();
150
+ rkuzu_init_config();
151
+ rkuzu_init_connection();
152
+ rkuzu_init_prepared_statement();
153
+ rkuzu_init_result();
154
+ rkuzu_init_query_summary();
155
+ rkuzu_init_node();
156
+ rkuzu_init_rel();
157
+ rkuzu_init_recursive_rel();
158
+ }