stoolap 0.4.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 +7 -0
- data/Cargo.lock +1792 -0
- data/Cargo.toml +9 -0
- data/LICENSE +201 -0
- data/README.md +477 -0
- data/ext/stoolap/Cargo.toml +20 -0
- data/ext/stoolap/extconf.rb +6 -0
- data/ext/stoolap/src/database.rs +432 -0
- data/ext/stoolap/src/error.rs +53 -0
- data/ext/stoolap/src/lib.rs +103 -0
- data/ext/stoolap/src/statement.rs +193 -0
- data/ext/stoolap/src/transaction.rs +246 -0
- data/ext/stoolap/src/value.rs +296 -0
- data/lib/stoolap/version.rb +5 -0
- data/lib/stoolap.rb +86 -0
- metadata +145 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// Copyright 2025 Stoolap Contributors
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
use std::cell::RefCell;
|
|
16
|
+
|
|
17
|
+
use chrono::{TimeZone, Utc};
|
|
18
|
+
use magnus::{
|
|
19
|
+
prelude::*,
|
|
20
|
+
r_hash::ForEach,
|
|
21
|
+
value::{Lazy, ReprValue},
|
|
22
|
+
Error, Float, Integer, RArray, RClass, RHash, RModule, RString, Ruby, Symbol, TryConvert,
|
|
23
|
+
Value,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
use stoolap::api::{NamedParams, ParamVec};
|
|
27
|
+
use stoolap::core::Value as SValue;
|
|
28
|
+
|
|
29
|
+
use crate::error::{raise, type_error};
|
|
30
|
+
|
|
31
|
+
/// Cached `Time` class. Lazy-initialised on first use; subsequent calls
|
|
32
|
+
/// are a direct pointer load, avoiding `const_get("Time")` on every
|
|
33
|
+
/// timestamp parameter and every timestamp result value.
|
|
34
|
+
static TIME_CLASS: Lazy<RClass> = Lazy::new(|ruby| {
|
|
35
|
+
ruby.class_object()
|
|
36
|
+
.const_get("Time")
|
|
37
|
+
.expect("Ruby core class Time must exist")
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/// Cached `:nsec` symbol used for `Time.at(secs, nsecs, :nsec)`.
|
|
41
|
+
static NSEC_SYM: Lazy<Symbol> = Lazy::new(|ruby| ruby.to_symbol("nsec"));
|
|
42
|
+
|
|
43
|
+
/// Cached `JSON` module used to serialise Hash/Array params.
|
|
44
|
+
/// `lib/stoolap.rb` does `require "json"` so the constant is
|
|
45
|
+
/// guaranteed to exist by the time any binding method runs. The
|
|
46
|
+
/// `.expect` is a last-resort guard, not a normal error path.
|
|
47
|
+
static JSON_MODULE: Lazy<RModule> = Lazy::new(|ruby| {
|
|
48
|
+
ruby.class_object()
|
|
49
|
+
.const_get("JSON")
|
|
50
|
+
.expect("JSON constant must exist (require \"json\" should have loaded it)")
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/// A vector of f32 values for similarity search.
|
|
54
|
+
///
|
|
55
|
+
/// Wraps a list of floats so that Stoolap stores them as a native VECTOR
|
|
56
|
+
/// rather than a JSON array.
|
|
57
|
+
///
|
|
58
|
+
/// @example
|
|
59
|
+
/// v = Stoolap::Vector.new([0.1, 0.2, 0.3])
|
|
60
|
+
/// db.execute("INSERT INTO t (embedding) VALUES ($1)", [v])
|
|
61
|
+
#[magnus::wrap(class = "Stoolap::Vector", free_immediately, size)]
|
|
62
|
+
pub struct Vector {
|
|
63
|
+
pub data: RefCell<Vec<f32>>,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
impl Vector {
|
|
67
|
+
pub fn new(data: RArray) -> Result<Self, Error> {
|
|
68
|
+
let mut floats: Vec<f32> = Vec::with_capacity(data.len());
|
|
69
|
+
for item in data.into_iter() {
|
|
70
|
+
let f: f64 = match TryConvert::try_convert(item) {
|
|
71
|
+
Ok(v) => v,
|
|
72
|
+
Err(_) => {
|
|
73
|
+
return Err(type_error("Vector elements must be numeric"));
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
floats.push(f as f32);
|
|
77
|
+
}
|
|
78
|
+
Ok(Self {
|
|
79
|
+
data: RefCell::new(floats),
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
pub fn to_a(&self) -> Vec<f64> {
|
|
84
|
+
self.data.borrow().iter().map(|f| *f as f64).collect()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
pub fn length(&self) -> usize {
|
|
88
|
+
self.data.borrow().len()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
pub fn inspect(&self) -> String {
|
|
92
|
+
let v = self.data.borrow();
|
|
93
|
+
let parts: Vec<String> = v.iter().map(|f| format!("{}", f)).collect();
|
|
94
|
+
format!("#<Stoolap::Vector [{}]>", parts.join(", "))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Internal: snapshot the contained data into a fresh Vec.
|
|
98
|
+
pub fn snapshot(&self) -> Vec<f32> {
|
|
99
|
+
self.data.borrow().clone()
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Parsed bind parameters from Ruby.
|
|
104
|
+
pub enum BindParams {
|
|
105
|
+
Positional(ParamVec),
|
|
106
|
+
Named(NamedParams),
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Convert a Ruby Value into a Stoolap Value.
|
|
110
|
+
pub fn ruby_to_value(val: Value) -> Result<SValue, Error> {
|
|
111
|
+
let ruby = magnus::Ruby::get().expect("must hold the Ruby VM lock");
|
|
112
|
+
|
|
113
|
+
if val.is_nil() {
|
|
114
|
+
return Ok(SValue::null_unknown());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Boolean
|
|
118
|
+
if val.is_kind_of(ruby.class_true_class()) {
|
|
119
|
+
return Ok(SValue::Boolean(true));
|
|
120
|
+
}
|
|
121
|
+
if val.is_kind_of(ruby.class_false_class()) {
|
|
122
|
+
return Ok(SValue::Boolean(false));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Integer
|
|
126
|
+
if let Some(i) = Integer::from_value(val) {
|
|
127
|
+
let n: i64 = i.to_i64()?;
|
|
128
|
+
return Ok(SValue::Integer(n));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Float
|
|
132
|
+
if let Some(f) = Float::from_value(val) {
|
|
133
|
+
return Ok(SValue::Float(f.to_f64()));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// String
|
|
137
|
+
if let Some(s) = RString::from_value(val) {
|
|
138
|
+
let owned = unsafe { s.as_str()?.to_owned() };
|
|
139
|
+
return Ok(SValue::text(owned));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Symbol -> string
|
|
143
|
+
if let Some(sym) = Symbol::from_value(val) {
|
|
144
|
+
return Ok(SValue::text(sym.name()?.into_owned()));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Stoolap::Vector wrapper
|
|
148
|
+
if let Ok(v) = <&Vector>::try_convert(val) {
|
|
149
|
+
return Ok(SValue::vector(v.snapshot()));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Time -> Timestamp (UTC)
|
|
153
|
+
let time_class = ruby.get_inner(&TIME_CLASS);
|
|
154
|
+
if val.is_kind_of(time_class) {
|
|
155
|
+
let secs: i64 = val.funcall("to_i", ())?;
|
|
156
|
+
let nsecs: i64 = val.funcall("nsec", ())?;
|
|
157
|
+
let dt = Utc
|
|
158
|
+
.timestamp_opt(secs, nsecs as u32)
|
|
159
|
+
.single()
|
|
160
|
+
.ok_or_else(|| raise("invalid Time value"))?;
|
|
161
|
+
return Ok(SValue::Timestamp(dt));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Array / Hash -> JSON
|
|
165
|
+
if RArray::from_value(val).is_some() || RHash::from_value(val).is_some() {
|
|
166
|
+
let json_module = ruby.get_inner(&JSON_MODULE);
|
|
167
|
+
let dumped: RString = json_module.funcall("dump", (val,))?;
|
|
168
|
+
let s = unsafe { dumped.as_str()?.to_owned() };
|
|
169
|
+
return Ok(SValue::json(s));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
Err(type_error(format!(
|
|
173
|
+
"Unsupported parameter type: {}",
|
|
174
|
+
val.class().inspect()
|
|
175
|
+
)))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/// Parse Ruby params (Array, Hash, or nil) into BindParams.
|
|
179
|
+
pub fn parse_params(params: Option<Value>) -> Result<BindParams, Error> {
|
|
180
|
+
let params = match params {
|
|
181
|
+
None => return Ok(BindParams::Positional(ParamVec::new())),
|
|
182
|
+
Some(v) if v.is_nil() => return Ok(BindParams::Positional(ParamVec::new())),
|
|
183
|
+
Some(v) => v,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Hash -> named params
|
|
187
|
+
if let Some(hash) = RHash::from_value(params) {
|
|
188
|
+
let mut named = NamedParams::new();
|
|
189
|
+
let mut err: Option<Error> = None;
|
|
190
|
+
let _ = hash.foreach(|key: Value, val: Value| {
|
|
191
|
+
// Extract the key as `&str` without an intermediate owned String.
|
|
192
|
+
// Symbols return a `Cow<str>` from `name()`; RStrings expose their
|
|
193
|
+
// bytes via `unsafe as_str` (valid while the Hash holds the key).
|
|
194
|
+
// A single `to_string` at `named.insert` is the only allocation.
|
|
195
|
+
let rstring = RString::from_value(key);
|
|
196
|
+
let symbol = Symbol::from_value(key);
|
|
197
|
+
|
|
198
|
+
let raw: &str = if let Some(sym) = symbol {
|
|
199
|
+
match sym.name() {
|
|
200
|
+
Ok(std::borrow::Cow::Borrowed(s)) => s,
|
|
201
|
+
// `sym.name()` returns Borrowed for pinned symbols in 3.x
|
|
202
|
+
// so the Owned branch should be unreachable in practice.
|
|
203
|
+
Ok(std::borrow::Cow::Owned(_)) => {
|
|
204
|
+
err = Some(type_error("dynamic symbol key not supported"));
|
|
205
|
+
return Ok(ForEach::Stop);
|
|
206
|
+
}
|
|
207
|
+
Err(e) => {
|
|
208
|
+
err = Some(e);
|
|
209
|
+
return Ok(ForEach::Stop);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} else if let Some(ref s) = rstring {
|
|
213
|
+
match unsafe { s.as_str() } {
|
|
214
|
+
Ok(s) => s,
|
|
215
|
+
Err(e) => {
|
|
216
|
+
err = Some(e);
|
|
217
|
+
return Ok(ForEach::Stop);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
err = Some(type_error("named parameter keys must be Symbol or String"));
|
|
222
|
+
return Ok(ForEach::Stop);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
let stripped = raw.trim_start_matches(&[':', '@', '$'][..]);
|
|
226
|
+
|
|
227
|
+
match ruby_to_value(val) {
|
|
228
|
+
Ok(v) => {
|
|
229
|
+
named.insert(stripped.to_string(), v);
|
|
230
|
+
Ok(ForEach::Continue)
|
|
231
|
+
}
|
|
232
|
+
Err(e) => {
|
|
233
|
+
err = Some(e);
|
|
234
|
+
Ok(ForEach::Stop)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
if let Some(e) = err {
|
|
239
|
+
return Err(e);
|
|
240
|
+
}
|
|
241
|
+
return Ok(BindParams::Named(named));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Array -> positional
|
|
245
|
+
if let Some(arr) = RArray::from_value(params) {
|
|
246
|
+
let mut values = ParamVec::new();
|
|
247
|
+
for item in arr.into_iter() {
|
|
248
|
+
values.push(ruby_to_value(item)?);
|
|
249
|
+
}
|
|
250
|
+
return Ok(BindParams::Positional(values));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
Err(type_error("Parameters must be an Array, Hash, or nil"))
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/// Convert a Stoolap Value to a Ruby Value.
|
|
257
|
+
pub fn value_to_ruby(val: &SValue) -> Result<Value, Error> {
|
|
258
|
+
let ruby = Ruby::get().expect("must hold the Ruby VM lock");
|
|
259
|
+
match val {
|
|
260
|
+
SValue::Null(_) => Ok(ruby.qnil().as_value()),
|
|
261
|
+
SValue::Boolean(b) => Ok(if *b {
|
|
262
|
+
ruby.qtrue().as_value()
|
|
263
|
+
} else {
|
|
264
|
+
ruby.qfalse().as_value()
|
|
265
|
+
}),
|
|
266
|
+
SValue::Integer(i) => Ok(ruby.integer_from_i64(*i).as_value()),
|
|
267
|
+
SValue::Float(f) => Ok(ruby.float_from_f64(*f).as_value()),
|
|
268
|
+
SValue::Text(s) => Ok(ruby.str_new(s.as_str()).as_value()),
|
|
269
|
+
SValue::Timestamp(ts) => {
|
|
270
|
+
// `Time.at(secs, nsecs, :nsec).utc` — Ruby's `Time#utc`
|
|
271
|
+
// mutates the receiver in place and returns self, so the
|
|
272
|
+
// chain is a single Time allocation. Cached Time class and
|
|
273
|
+
// :nsec symbol avoid const_get + intern per-row.
|
|
274
|
+
let time_class = ruby.get_inner(&TIME_CLASS);
|
|
275
|
+
let nsec_sym = ruby.get_inner(&NSEC_SYM);
|
|
276
|
+
let secs = ts.timestamp();
|
|
277
|
+
let nsecs = ts.timestamp_subsec_nanos() as i64;
|
|
278
|
+
let t: Value = time_class.funcall("at", (secs, nsecs, nsec_sym))?;
|
|
279
|
+
let utc: Value = t.funcall("utc", ())?;
|
|
280
|
+
Ok(utc)
|
|
281
|
+
}
|
|
282
|
+
SValue::Extension(_) => {
|
|
283
|
+
if let Some(floats) = val.as_vector_f32() {
|
|
284
|
+
let arr = ruby.ary_new_capa(floats.len());
|
|
285
|
+
for f in floats.iter() {
|
|
286
|
+
arr.push(*f as f64)?;
|
|
287
|
+
}
|
|
288
|
+
Ok(arr.as_value())
|
|
289
|
+
} else if let Some(s) = val.as_json() {
|
|
290
|
+
Ok(ruby.str_new(s).as_value())
|
|
291
|
+
} else {
|
|
292
|
+
Ok(ruby.str_new(&format!("{}", val)).as_value())
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
data/lib/stoolap.rb
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "stoolap/version"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
# Native extension. Tries pre-compiled binary first (per-Ruby-ABI),
|
|
7
|
+
# falls back to a single-arch build at lib/stoolap/stoolap.<ext>.
|
|
8
|
+
begin
|
|
9
|
+
RUBY_VERSION =~ /(\d+\.\d+)/
|
|
10
|
+
require_relative "stoolap/#{Regexp.last_match(1)}/stoolap"
|
|
11
|
+
rescue LoadError
|
|
12
|
+
require_relative "stoolap/stoolap"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Stoolap is a high-performance embedded SQL database for Ruby.
|
|
16
|
+
#
|
|
17
|
+
# @example In-memory database
|
|
18
|
+
# db = Stoolap::Database.open(":memory:")
|
|
19
|
+
# db.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
|
|
20
|
+
# db.execute("INSERT INTO users VALUES ($1, $2)", [1, "Alice"])
|
|
21
|
+
# rows = db.query("SELECT * FROM users")
|
|
22
|
+
# # => [{"id" => 1, "name" => "Alice"}]
|
|
23
|
+
#
|
|
24
|
+
# @example File-backed database
|
|
25
|
+
# db = Stoolap::Database.open("./mydata")
|
|
26
|
+
#
|
|
27
|
+
# @example Block form (auto-closes)
|
|
28
|
+
# Stoolap::Database.open(":memory:") do |db|
|
|
29
|
+
# db.exec("CREATE TABLE t (id INTEGER)")
|
|
30
|
+
# end
|
|
31
|
+
module Stoolap
|
|
32
|
+
class Database
|
|
33
|
+
# Open a database. If a block is given, the database is closed
|
|
34
|
+
# automatically when the block returns (even on exception).
|
|
35
|
+
#
|
|
36
|
+
# @param path [String] DSN or file path. Use ":memory:" for in-memory.
|
|
37
|
+
# @yieldparam db [Database]
|
|
38
|
+
# @return [Database, Object] the database, or the block's return value
|
|
39
|
+
def self.open(path = ":memory:")
|
|
40
|
+
db = _open(path)
|
|
41
|
+
return db unless block_given?
|
|
42
|
+
|
|
43
|
+
begin
|
|
44
|
+
yield db
|
|
45
|
+
ensure
|
|
46
|
+
db.close
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Begin a transaction. If a block is given, the transaction commits
|
|
51
|
+
# on clean exit and rolls back on exception.
|
|
52
|
+
#
|
|
53
|
+
# @yieldparam tx [Transaction]
|
|
54
|
+
# @return [Transaction, Object]
|
|
55
|
+
def transaction
|
|
56
|
+
tx = begin_transaction
|
|
57
|
+
return tx unless block_given?
|
|
58
|
+
|
|
59
|
+
committed = false
|
|
60
|
+
begin
|
|
61
|
+
result = yield tx
|
|
62
|
+
tx.commit
|
|
63
|
+
committed = true
|
|
64
|
+
result
|
|
65
|
+
ensure
|
|
66
|
+
# Roll back on ANY exception (including Interrupt, SystemExit,
|
|
67
|
+
# and direct Exception subclasses), not just StandardError.
|
|
68
|
+
tx.rollback unless committed
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class Transaction
|
|
74
|
+
# Yield this transaction; commit on success, rollback on exception.
|
|
75
|
+
def with_rollback
|
|
76
|
+
committed = false
|
|
77
|
+
begin
|
|
78
|
+
yield self
|
|
79
|
+
commit
|
|
80
|
+
committed = true
|
|
81
|
+
ensure
|
|
82
|
+
rollback unless committed
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: stoolap
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.4.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Stoolap Contributors
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-04-10 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rb_sys
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 0.9.91
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 0.9.91
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '13.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '13.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake-compiler
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '1.2'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '1.2'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: minitest
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '5.20'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '5.20'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: simplecov
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0.22'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0.22'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: sqlite3
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '2.0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '2.0'
|
|
97
|
+
description: Native Ruby driver for the Stoolap embedded SQL database. Built with
|
|
98
|
+
Magnus + rb-sys for zero-FFI Rust performance.
|
|
99
|
+
email:
|
|
100
|
+
executables: []
|
|
101
|
+
extensions:
|
|
102
|
+
- ext/stoolap/extconf.rb
|
|
103
|
+
extra_rdoc_files: []
|
|
104
|
+
files:
|
|
105
|
+
- Cargo.lock
|
|
106
|
+
- Cargo.toml
|
|
107
|
+
- LICENSE
|
|
108
|
+
- README.md
|
|
109
|
+
- ext/stoolap/Cargo.toml
|
|
110
|
+
- ext/stoolap/extconf.rb
|
|
111
|
+
- ext/stoolap/src/database.rs
|
|
112
|
+
- ext/stoolap/src/error.rs
|
|
113
|
+
- ext/stoolap/src/lib.rs
|
|
114
|
+
- ext/stoolap/src/statement.rs
|
|
115
|
+
- ext/stoolap/src/transaction.rs
|
|
116
|
+
- ext/stoolap/src/value.rs
|
|
117
|
+
- lib/stoolap.rb
|
|
118
|
+
- lib/stoolap/version.rb
|
|
119
|
+
homepage: https://stoolap.io
|
|
120
|
+
licenses:
|
|
121
|
+
- Apache-2.0
|
|
122
|
+
metadata:
|
|
123
|
+
source_code_uri: https://github.com/stoolap/stoolap-ruby
|
|
124
|
+
documentation_uri: https://stoolap.io/docs/drivers/ruby/
|
|
125
|
+
bug_tracker_uri: https://github.com/stoolap/stoolap-ruby/issues
|
|
126
|
+
post_install_message:
|
|
127
|
+
rdoc_options: []
|
|
128
|
+
require_paths:
|
|
129
|
+
- lib
|
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
131
|
+
requirements:
|
|
132
|
+
- - ">="
|
|
133
|
+
- !ruby/object:Gem::Version
|
|
134
|
+
version: 3.1.0
|
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
136
|
+
requirements:
|
|
137
|
+
- - ">="
|
|
138
|
+
- !ruby/object:Gem::Version
|
|
139
|
+
version: 3.3.11
|
|
140
|
+
requirements: []
|
|
141
|
+
rubygems_version: 3.5.22
|
|
142
|
+
signing_key:
|
|
143
|
+
specification_version: 4
|
|
144
|
+
summary: High-performance embedded SQL database for Ruby
|
|
145
|
+
test_files: []
|