gqlite 1.0.0 → 1.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.
@@ -0,0 +1,46 @@
1
+ # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
2
+ #
3
+ # When uploading crates to the registry Cargo will automatically
4
+ # "normalize" Cargo.toml files for maximal compatibility
5
+ # with all versions of Cargo and also rewrite `path` dependencies
6
+ # to registry (e.g., crates.io) dependencies.
7
+ #
8
+ # If you are reading this file be aware that the original Cargo.toml
9
+ # will likely look very different (and much more reasonable).
10
+ # See Cargo.toml.orig for the original contents.
11
+
12
+ [package]
13
+ edition = "2021"
14
+ name = "gqliterb"
15
+ version = "0.5.0"
16
+ build = false
17
+ autolib = false
18
+ autobins = false
19
+ autoexamples = false
20
+ autotests = false
21
+ autobenches = false
22
+ homepage = "https://gqlite.org"
23
+ readme = false
24
+ license = "MIT"
25
+ repository = "https://gitlab.com/gqlite/gqlite"
26
+
27
+ [package.metadata.release]
28
+ release = false
29
+
30
+ [lib]
31
+ name = "gqliterb"
32
+ crate-type = ["cdylib"]
33
+ path = "src/lib.rs"
34
+
35
+ [dependencies.gqlitedb]
36
+ version = "0.5.0"
37
+ features = [
38
+ "_connection_server",
39
+ "_backtrace",
40
+ ]
41
+
42
+ [dependencies.magnus]
43
+ version = "0.7"
44
+
45
+ [build-dependencies.rb-sys-env]
46
+ version = "0.2"
@@ -0,0 +1,4 @@
1
+ require "mkmf"
2
+ require "rb_sys/mkmf"
3
+
4
+ create_rust_makefile "gqliterb"
@@ -0,0 +1,260 @@
1
+ #![deny(warnings)]
2
+
3
+ use magnus::{
4
+ function, method,
5
+ prelude::*,
6
+ r_array,
7
+ r_hash::{self, ForEach},
8
+ scan_args,
9
+ value::Lazy,
10
+ Error, ExceptionClass, Integer, IntoValue, RModule, Ruby, Symbol,
11
+ };
12
+
13
+ static MODULE: Lazy<RModule> = Lazy::new(|ruby| ruby.define_module("GQLite").unwrap());
14
+
15
+ static ERROR: Lazy<ExceptionClass> = Lazy::new(|ruby| {
16
+ ruby
17
+ .get_inner(&MODULE)
18
+ .define_error("Error", ruby.exception_standard_error())
19
+ .unwrap()
20
+ });
21
+
22
+ fn from_rvalue(ruby: &Ruby, value: magnus::Value) -> Result<gqlitedb::Value, Error>
23
+ {
24
+ if value.is_nil()
25
+ {
26
+ Ok(gqlitedb::Value::Null)
27
+ }
28
+ else if value.is_kind_of(ruby.class_integer())
29
+ {
30
+ Ok(i64::try_convert(value)?.into())
31
+ }
32
+ else if value.is_kind_of(ruby.class_true_class())
33
+ {
34
+ Ok(true.into())
35
+ }
36
+ else if value.is_kind_of(ruby.class_false_class())
37
+ {
38
+ Ok(false.into())
39
+ }
40
+ else if value.is_kind_of(ruby.class_float())
41
+ {
42
+ Ok(f64::try_convert(value)?.into())
43
+ }
44
+ else if value.is_kind_of(ruby.class_string())
45
+ {
46
+ Ok(String::try_convert(value)?.into())
47
+ }
48
+ else if value.is_kind_of(ruby.class_hash())
49
+ {
50
+ Ok(from_rhash(ruby, r_hash::RHash::try_convert(value)?)?.into())
51
+ }
52
+ else if value.is_kind_of(ruby.class_array())
53
+ {
54
+ Ok(from_rarray(ruby, r_array::RArray::try_convert(value)?)?.into())
55
+ }
56
+ else
57
+ {
58
+ Err(Error::new(
59
+ ruby.get_inner(&ERROR),
60
+ format!(
61
+ "Cannot convert a value of type '{}' to a GQLite value.",
62
+ value.class()
63
+ ),
64
+ ))
65
+ }
66
+ }
67
+
68
+ fn from_rarray(ruby: &Ruby, array: r_array::RArray) -> Result<Vec<gqlitedb::Value>, Error>
69
+ {
70
+ array
71
+ .into_iter()
72
+ .map(|value| from_rvalue(ruby, value))
73
+ .collect()
74
+ }
75
+
76
+ fn from_rhash(ruby: &Ruby, hash: r_hash::RHash) -> Result<gqlitedb::ValueMap, Error>
77
+ {
78
+ let mut vmap = gqlitedb::ValueMap::new();
79
+ hash.foreach(|key: magnus::Value, value: magnus::Value| {
80
+ let key = if key.is_kind_of(ruby.class_symbol())
81
+ {
82
+ Symbol::try_convert(key)?.name()?.into()
83
+ }
84
+ else
85
+ {
86
+ String::try_convert(key)?
87
+ };
88
+
89
+ vmap.insert(key, from_rvalue(ruby, value)?);
90
+ Ok(ForEach::Continue)
91
+ })?;
92
+
93
+ Ok(vmap)
94
+ }
95
+
96
+ fn integer_from_u128(ruby: &Ruby, i: u128) -> Result<Integer, Error>
97
+ {
98
+ if i <= u64::MAX as u128
99
+ {
100
+ Ok(ruby.integer_from_u64(i as u64))
101
+ }
102
+ else
103
+ {
104
+ ruby.module_kernel().funcall("Integer", (i.to_string(),))
105
+ }
106
+ }
107
+
108
+ fn node_to_rhash(ruby: &Ruby, node: gqlitedb::Node) -> Result<magnus::Value, Error>
109
+ {
110
+ let r_hash = ruby.hash_new();
111
+ let (key, labels, properties) = node.unpack();
112
+ r_hash.aset("type", "node")?;
113
+ r_hash.aset("key", integer_from_u128(ruby, key.into())?)?;
114
+ r_hash.aset("labels", labels)?;
115
+ r_hash.aset("properties", to_rhash(ruby, properties)?)?;
116
+ Ok(r_hash.into_value())
117
+ }
118
+
119
+ fn edge_to_rhash(ruby: &Ruby, edge: gqlitedb::Edge) -> Result<magnus::Value, Error>
120
+ {
121
+ let r_hash = ruby.hash_new();
122
+ let (key, labels, properties) = edge.unpack();
123
+ r_hash.aset("type", "edge")?;
124
+ r_hash.aset("key", integer_from_u128(ruby, key.into())?)?;
125
+ r_hash.aset("labels", labels)?;
126
+ r_hash.aset("properties", to_rhash(ruby, properties)?)?;
127
+ Ok(r_hash.into_value())
128
+ }
129
+
130
+ fn path_to_rhash(ruby: &Ruby, path: gqlitedb::Path) -> Result<magnus::Value, Error>
131
+ {
132
+ let r_hash = ruby.hash_new();
133
+ let (key, source, labels, properties, destination) = path.unpack();
134
+ r_hash.aset("type", "path")?;
135
+ r_hash.aset("key", integer_from_u128(ruby, key.into())?)?;
136
+ r_hash.aset("labels", labels)?;
137
+ r_hash.aset("properties", to_rhash(ruby, properties)?)?;
138
+ r_hash.aset("source", node_to_rhash(ruby, source)?)?;
139
+ r_hash.aset("destination", node_to_rhash(ruby, destination)?)?;
140
+ Ok(r_hash.into_value())
141
+ }
142
+
143
+ fn to_rvalue(ruby: &Ruby, val: gqlitedb::Value) -> Result<magnus::Value, Error>
144
+ {
145
+ match val
146
+ {
147
+ gqlitedb::Value::Array(arr) => Ok(to_rarray(ruby, arr)?.into_value()),
148
+ gqlitedb::Value::Boolean(b) => Ok(b.into_value()),
149
+ gqlitedb::Value::Integer(i) => Ok(i.into_value()),
150
+ gqlitedb::Value::Float(f) => Ok(f.into_value()),
151
+ gqlitedb::Value::String(s) => Ok(s.into_value()),
152
+ gqlitedb::Value::Map(m) => Ok(to_rhash(ruby, m)?.into_value()),
153
+ gqlitedb::Value::Null => Ok(ruby.qnil().into_value()),
154
+ gqlitedb::Value::Edge(e) => Ok(edge_to_rhash(ruby, e)?),
155
+ gqlitedb::Value::Node(n) => Ok(node_to_rhash(ruby, n)?),
156
+ gqlitedb::Value::Path(p) => Ok(path_to_rhash(ruby, p)?),
157
+ }
158
+ }
159
+
160
+ fn to_rhash(ruby: &Ruby, map: gqlitedb::ValueMap) -> Result<r_hash::RHash, Error>
161
+ {
162
+ let r_hash = ruby.hash_new();
163
+ for (key, value) in map.into_iter()
164
+ {
165
+ r_hash.aset(key, to_rvalue(ruby, value)?)?;
166
+ }
167
+ Ok(r_hash)
168
+ }
169
+
170
+ fn to_rarray(ruby: &Ruby, arr: Vec<gqlitedb::Value>) -> Result<r_array::RArray, Error>
171
+ {
172
+ let r_arr = r_array::RArray::with_capacity(arr.len());
173
+
174
+ for value in arr.into_iter()
175
+ {
176
+ r_arr.push(to_rvalue(ruby, value)?)?;
177
+ }
178
+
179
+ Ok(r_arr)
180
+ }
181
+
182
+ fn map_err<T>(ruby: &Ruby, result: gqlitedb::Result<T>) -> Result<T, Error>
183
+ {
184
+ result.map_err(|e| Error::new(ruby.get_inner(&ERROR), format!("{}", e)))
185
+ }
186
+
187
+ #[magnus::wrap(class = "GQLite::Connection")]
188
+ struct Connection
189
+ {
190
+ dbhandle: gqlitedb::ConnectionServer,
191
+ }
192
+
193
+ impl Connection
194
+ {
195
+ fn new(ruby: &Ruby, args: &[magnus::Value]) -> Result<Self, Error>
196
+ {
197
+ let args = scan_args::scan_args::<(), (), (), (), _, ()>(args)?;
198
+
199
+ let options = from_rhash(ruby, args.keywords)?;
200
+
201
+ let filename: String = map_err(
202
+ ruby,
203
+ options
204
+ .get("filename".into())
205
+ .ok_or_else(|| Error::new(ruby.get_inner(&ERROR), "Missing filename."))?
206
+ .to_owned()
207
+ .try_into(),
208
+ )?;
209
+ let dbhandle = map_err(ruby, gqlitedb::ConnectionServer::open(filename, options))?;
210
+ Ok(Self { dbhandle })
211
+ }
212
+ fn execute_oc_query(
213
+ ruby: &Ruby,
214
+ rb_self: &Self,
215
+ args: &[magnus::Value],
216
+ ) -> Result<magnus::Value, Error>
217
+ {
218
+ let args = scan_args::scan_args::<_, (), (), (), _, ()>(args)?;
219
+ let (query,): (String,) = args.required;
220
+
221
+ let kw = scan_args::get_kwargs::<_, (), (Option<magnus::Value>,), ()>(
222
+ args.keywords,
223
+ &[],
224
+ &["bindings"],
225
+ )?;
226
+ let (bindings,) = kw.optional;
227
+
228
+ let bindings = bindings
229
+ .map(|bindings| {
230
+ if bindings.is_nil()
231
+ {
232
+ Ok(Default::default())
233
+ }
234
+ else
235
+ {
236
+ from_rhash(ruby, r_hash::RHash::try_convert(bindings)?)
237
+ }
238
+ })
239
+ .transpose()?
240
+ .unwrap_or_default();
241
+ let result = map_err(ruby, rb_self.dbhandle.execute_query(query, bindings))?;
242
+
243
+ to_rvalue(ruby, result)
244
+ }
245
+ }
246
+
247
+ #[magnus::init]
248
+ fn init(ruby: &Ruby) -> Result<(), Error>
249
+ {
250
+ Lazy::force(&ERROR, ruby);
251
+
252
+ let module = ruby.get_inner(&MODULE);
253
+ let class = module.define_class("Connection", ruby.class_object())?;
254
+ class.define_singleton_method("new", function!(Connection::new, -1))?;
255
+ class.define_method(
256
+ "execute_oc_query",
257
+ method!(Connection::execute_oc_query, -1),
258
+ )?;
259
+ Ok(())
260
+ }
data/lib/gqlite.rb CHANGED
@@ -1,75 +1 @@
1
- require 'ffi'
2
- require 'objspace'
3
- require 'json'
4
-
5
- module GQLite
6
- class Error < StandardError
7
- end
8
- module CApi
9
- extend FFI::Library
10
-
11
- # Attempt to find the gqlite build that is shipped with gem
12
- def CApi.get_lib_name()
13
- path = "#{File.dirname __FILE__}/#{FFI::Platform::LIBPREFIX}gqlite.#{FFI::Platform::LIBSUFFIX}"
14
- return path if File.exist?(path)
15
- path = "#{File.dirname __FILE__}/gqlite.#{FFI::Platform::LIBSUFFIX}"
16
- return path if File.exist?(path)
17
- return "gqlite"
18
- end
19
-
20
- ffi_lib CApi.get_lib_name()
21
- attach_function :gqlite_api_context_create, [], :pointer
22
- attach_function :gqlite_api_context_destroy, [:pointer], :void
23
- attach_function :gqlite_api_context_clear_error, [:pointer], :void
24
- attach_function :gqlite_api_context_has_error, [:pointer], :bool
25
- attach_function :gqlite_api_context_get_message, [:pointer], :string
26
- attach_function :gqlite_connection_create_from_sqlite_file, [:pointer, :string, :pointer], :pointer
27
- attach_function :gqlite_connection_destroy, [:pointer, :pointer], :void
28
- attach_function :gqlite_connection_oc_query, [:pointer, :pointer, :string, :pointer], :pointer
29
- attach_function :gqlite_value_create, [:pointer], :pointer
30
- attach_function :gqlite_value_destroy, [:pointer, :pointer], :void
31
- attach_function :gqlite_value_to_json, [:pointer, :pointer], :string
32
- attach_function :gqlite_value_from_json, [:pointer, :string], :pointer
33
- attach_function :gqlite_value_is_valid, [:pointer, :pointer], :bool
34
- ApiContext = CApi.gqlite_api_context_create()
35
- def CApi.call_function(fname, *args)
36
- r = CApi.send fname, ApiContext, *args
37
- if CApi.gqlite_api_context_has_error(ApiContext)
38
- err = CApi.gqlite_api_context_get_message ApiContext
39
- CApi.gqlite_api_context_clear_error ApiContext
40
- raise Error.new err
41
- end
42
- return r
43
- end
44
- end
45
- class Connection
46
- attr_reader :dbhandle
47
- def initialize(sqlite_filename: nil)
48
- if sqlite_filename != nil
49
- @dbhandle = CApi.call_function :gqlite_connection_create_from_sqlite_file, sqlite_filename, nil
50
- else
51
- raise Error.new "No connection backend was selected."
52
- end
53
- ObjectSpace.define_finalizer @dbhandle, proc {|id|
54
- CApi.call_function :gqlite_connection_destroy, @dbhandle
55
- }
56
- end
57
- def execute_oc_query(query, bindings: nil)
58
- b = nil
59
- if bindings
60
- b = CApi.call_function :gqlite_value_from_json, bindings.to_json
61
- end
62
- ret = CApi.call_function :gqlite_connection_oc_query, @dbhandle, query, b
63
- if b
64
- CApi.call_function :gqlite_value_destroy, b
65
- end
66
- if CApi.call_function(:gqlite_value_is_valid, ret)
67
- val = JSON.parse(CApi.call_function :gqlite_value_to_json, ret)
68
- else
69
- val = nil
70
- end
71
- CApi.call_function :gqlite_value_destroy, ret
72
- return val
73
- end
74
- end
75
- end
1
+ require 'gqliterb'
metadata CHANGED
@@ -1,60 +1,73 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gqlite
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyrille Berger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-17 00:00:00.000000000 Z
11
+ date: 2025-07-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: ffi
14
+ name: rb_sys
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.15'
19
+ version: 0.9.39
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.15'
27
- description: "GQLite is a C++-language library, with a C interface, that implements
26
+ version: 0.9.39
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake-compiler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.2.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.2.0
41
+ description: "GQLite is a Rust-language library, with a C interface, that implements
28
42
  a small, fast, self-contained, high-reliability, full-featured, Graph Query database
29
- engine. The data is stored in a SQLite database, which the fasted and most used
30
- SQL database. This enable to achieve high performance and for application to combine
31
- Graph queries with traditional SQL queries.\n\nGQLite source code is license under
32
- the [MIT License](LICENSE) and is free to everyone to use for any purpose. \n\nThe
33
- official repositories contains bindings/APIs for C, C++, Python, Ruby and Crystal.\n\nThe
34
- library is still in its early stage, but it is now fully functional. Development
35
- effort has now slowed down and new features are added on a by-need basis. It supports
36
- a subset of [OpenCypher](https://opencypher.org/), and the intent is to also support
37
- ISO GQL in the future when it become available.\n \nExample of use\n--------------\n\n```ruby\nrequire
43
+ engine.\nGQLite support multiple database backends, such as SQLite and redb.\nThis
44
+ enable to achieve high performance and for application to combine Graph queries
45
+ with traditional SQL queries.\n\nGQLite source code is license under the [MIT License](LICENSE)
46
+ and is free to everyone to use for any purpose. \n\nThe official repositories contains
47
+ bindings/APIs for C, C++, Python, Ruby and Crystal.\n\nThe library is still in its
48
+ early stage, but it is now fully functional. Development effort has now slowed down
49
+ and new features are added on a by-need basis. It supports a subset of OpenCypher,
50
+ with some ISO GQL extensions.\n \nExample of use\n--------------\n\n```ruby\nrequire
38
51
  'gqlite'\n\nbegin\n # Create a database on the file \"test.db\"\n connection =
39
- GQLite::Connection.new sqlite_filename: \"test.db\"\n\n # Execute a simple query
40
- to create a node and return all the nodes\n value = connection.execute_oc_query(\"CREATE
52
+ GQLite::Connection.new filename: \"test.db\"\n\n # Execute a simple query to create
53
+ a node and return all the nodes\n value = connection.execute_oc_query(\"CREATE
41
54
  () MATCH (n) RETURN n\")\n\n # Print the result\n if value.nil?\n puts \"Empty
42
55
  results\"\n else\n puts \"Results are #{value.to_s}\"\n end\nrescue GQLite::Error
43
56
  => ex\n # Report any error\n puts \"An error has occured: #{ex.message}\"\nend\n\n```\n\nThe
44
- documentation for the openCypher query language can found in [openCypher](https://gitlab.com/gqlite/GQLite/-/blob/docs/opencypher.md)
45
- and for the [API](https://gitlab.com/gqlite/GQLite/-/blob/docs/api.md).\n\n"
57
+ documentation for the GQL query language can found in [OpenCypher](https://auksys.org/documentation/5/libraries/gqlite/opencypher/)
58
+ and for the [API](https://auksys.org/documentation/5/libraries/gqlite/api/).\n\n"
46
59
  email:
47
60
  executables: []
48
61
  extensions:
49
- - ext/gqlite/extconf.rb
62
+ - ext/gqliterb/extconf.rb
50
63
  extra_rdoc_files: []
51
64
  files:
52
- - ext/gqlite/extconf.rb
53
- - ext/gqlite/gqlite-amalgamate.cpp
54
- - ext/gqlite/gqlite-c.h
55
- - ext/gqlite/gqlite.h
65
+ - ext/gqliterb/Cargo.lock
66
+ - ext/gqliterb/Cargo.toml
67
+ - ext/gqliterb/extconf.rb
68
+ - ext/gqliterb/src/lib.rs
56
69
  - lib/gqlite.rb
57
- homepage: https://gitlab.com/gqlite/gqlite
70
+ homepage: https://gitlab.com/auksys/gqlite
58
71
  licenses:
59
72
  - MIT
60
73
  metadata: {}
@@ -1,21 +0,0 @@
1
- require "mkmf"
2
-
3
- # Build the gqlite library
4
-
5
- # Platforms check
6
- IS_MSWIN = !RbConfig::CONFIG['host_os'].match(/mswin/).nil?
7
- IS_MINGW = !RbConfig::CONFIG['host_os'].match(/mingw/).nil?
8
- IS_DARWIN = !RbConfig::CONFIG['host_os'].match(/darwin/).nil?
9
-
10
- # gqlite
11
- if IS_MSWIN
12
- $CXXFLAGS += " /std:c++20 /EHsc /permissive- /bigobj"
13
- elsif IS_MINGW
14
- $CXXFLAGS += " -std=c++20 -Wa,-mbig-obj"
15
- $LDFLAGS += " -lsqlite3 "
16
- else
17
- $CXXFLAGS += " -std=c++20"
18
- $LDFLAGS += " -lsqlite3 "
19
- end
20
-
21
- create_makefile "gqlite"