galaaz 2.1.0 → 2.1.2
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
- data/CHANGELOG.md +38 -4
- data/README.md +79 -32
- data/bin/galaaz +10 -2
- data/bin/galaaz-jruby +0 -0
- data/bin/galaaz-ruby +0 -0
- data/bin/galaaz_jruby_env.inc.sh +19 -3
- data/bin/galaaz_ruby_env.inc.sh +2 -0
- data/bin/gbookdown +0 -0
- data/blogs/R-on-Rails-Planning-Document.md +79 -112
- data/blogs/manual/manual.md +79 -32
- data/ext/new_bridge/Makefile +10 -10
- data/lib/R_interface/r_arrow.rb +37 -0
- data/lib/galaaz/arrow_ipc/java_arrow_backend.rb +250 -0
- data/lib/galaaz/arrow_ipc/red_arrow_backend.rb +126 -0
- data/lib/galaaz/arrow_ipc.rb +167 -0
- data/lib/galaaz/cli.rb +552 -0
- data/lib/galaaz.rb +6 -0
- data/lib/galaaz_jruby.rb +17 -2
- data/new_bridge_specs/arrow_ipc_async_spec.rb +90 -0
- data/new_bridge_specs/arrow_ipc_export_async_spec.rb +68 -0
- data/r_requires/arrow.txt +3 -0
- data/r_requires/knit-extras.txt +4 -0
- data/r_requires/knit.txt +4 -0
- data/specs/arrow_ipc_export_spec.rb +62 -0
- data/specs/arrow_ipc_handoff_spec.rb +97 -0
- data/version.rb +1 -1
- metadata +17 -9
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Async synchronization for Stage B2 R→Ruby Arrow IPC export.
|
|
4
|
+
# Run: bin/run_all_rspec new_bridge_specs/arrow_ipc_export_async_spec.rb
|
|
5
|
+
|
|
6
|
+
require 'thread'
|
|
7
|
+
require 'galaaz'
|
|
8
|
+
|
|
9
|
+
RSpec.describe 'Arrow IPC export async (Stage B2)' do
|
|
10
|
+
before(:all) do
|
|
11
|
+
skip 'R not on PATH' unless system('command -v R >/dev/null 2>&1')
|
|
12
|
+
skip 'Galaaz::ArrowIpc backend not available' unless Galaaz::ArrowIpc.available?
|
|
13
|
+
ok = R::Support.eval("requireNamespace('arrow', quietly=TRUE)")
|
|
14
|
+
skip 'R package arrow is not available' unless ok == true
|
|
15
|
+
@paths = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
after(:each) do
|
|
19
|
+
Array(@paths).each { |p| Galaaz::ArrowIpc.release(p) }
|
|
20
|
+
@paths = []
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def track(path)
|
|
24
|
+
@paths << path
|
|
25
|
+
path
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'happens-before: async R write_ipc_file then Ruby read matches' do
|
|
29
|
+
in_path = track(Galaaz::ArrowIpc.write(n: [1, 2, 3]))
|
|
30
|
+
out_path = track(Galaaz::ArrowIpc.allocate_path)
|
|
31
|
+
|
|
32
|
+
done = Queue.new
|
|
33
|
+
R.eval_r_async(
|
|
34
|
+
"({ arrow::write_ipc_file(arrow::read_ipc_file(#{in_path.inspect}, as_data_frame=FALSE), #{out_path.inspect}, compression='uncompressed'); 1L })",
|
|
35
|
+
timeout: 30
|
|
36
|
+
) { |result| done.push(result) }
|
|
37
|
+
r = done.pop
|
|
38
|
+
expect(r).to be_ok
|
|
39
|
+
|
|
40
|
+
cols = Galaaz::ArrowIpc.read(out_path)
|
|
41
|
+
expect(cols['n'].map(&:to_i)).to eq([1, 2, 3])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'two concurrent R exports do not cross-talk' do
|
|
45
|
+
in_a = track(Galaaz::ArrowIpc.write(tag: ['A']))
|
|
46
|
+
in_b = track(Galaaz::ArrowIpc.write(tag: ['B']))
|
|
47
|
+
out_a = track(Galaaz::ArrowIpc.allocate_path)
|
|
48
|
+
out_b = track(Galaaz::ArrowIpc.allocate_path)
|
|
49
|
+
|
|
50
|
+
done = Queue.new
|
|
51
|
+
R.eval_r_async(
|
|
52
|
+
"({ arrow::write_ipc_file(arrow::read_ipc_file(#{in_a.inspect}, as_data_frame=FALSE), #{out_a.inspect}, compression='uncompressed'); 1L })",
|
|
53
|
+
timeout: 30
|
|
54
|
+
) { |res| done.push([:a, res]) }
|
|
55
|
+
R.eval_r_async(
|
|
56
|
+
"({ arrow::write_ipc_file(arrow::read_ipc_file(#{in_b.inspect}, as_data_frame=FALSE), #{out_b.inspect}, compression='uncompressed'); 1L })",
|
|
57
|
+
timeout: 30
|
|
58
|
+
) { |res| done.push([:b, res]) }
|
|
59
|
+
|
|
60
|
+
2.times do
|
|
61
|
+
_key, res = done.pop
|
|
62
|
+
expect(res).to be_ok
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
expect(Galaaz::ArrowIpc.read(out_a)['tag']).to eq(['A'])
|
|
66
|
+
expect(Galaaz::ArrowIpc.read(out_b)['tag']).to eq(['B'])
|
|
67
|
+
end
|
|
68
|
+
end
|
data/r_requires/knit.txt
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'galaaz'
|
|
4
|
+
|
|
5
|
+
describe 'Arrow IPC export (Stage B2)' do
|
|
6
|
+
before(:all) do
|
|
7
|
+
skip 'Galaaz::ArrowIpc backend not available' unless Galaaz::ArrowIpc.available?
|
|
8
|
+
ok = R::Support.eval("requireNamespace('arrow', quietly=TRUE) && requireNamespace('dplyr', quietly=TRUE)")
|
|
9
|
+
skip 'R packages arrow/dplyr are not available' unless ok == true
|
|
10
|
+
@paths = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
after(:each) do
|
|
14
|
+
Array(@paths).each { |p| Galaaz::ArrowIpc.release(p) }
|
|
15
|
+
@paths = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def track(path)
|
|
19
|
+
@paths << path
|
|
20
|
+
path
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'reads columns R wrote via write_ipc (R→Ruby)' do
|
|
24
|
+
in_path = track(Galaaz::ArrowIpc.write(value: [1.5, 2.5], grp: %w[a b]))
|
|
25
|
+
tbl = R::Arrow.open_ipc(in_path)
|
|
26
|
+
out_path = track(R::Arrow.write_ipc(tbl))
|
|
27
|
+
expect(File.exist?(out_path)).to be true
|
|
28
|
+
|
|
29
|
+
cols = Galaaz::ArrowIpc.read(out_path)
|
|
30
|
+
expect(cols['value']).to eq([1.5, 2.5])
|
|
31
|
+
expect(cols['grp']).to eq(%w[a b])
|
|
32
|
+
|
|
33
|
+
rows = Galaaz::ArrowIpc.read_batches(out_path)
|
|
34
|
+
expect(rows).to eq([{ value: 1.5, grp: 'a' }, { value: 2.5, grp: 'b' }])
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'proves DB-style loop: Ruby ingest → R summarise → Ruby reads result table' do
|
|
38
|
+
in_path = track(Galaaz::ArrowIpc.write_batches([
|
|
39
|
+
{ id: 1, grp: 'a', value: 10.0 },
|
|
40
|
+
{ id: 2, grp: 'b', value: 20.5 },
|
|
41
|
+
{ id: 3, grp: 'a', value: 30.0 }
|
|
42
|
+
]))
|
|
43
|
+
tbl = R::Arrow.open_ipc(in_path)
|
|
44
|
+
grouped = R.dplyr___group_by(tbl, :grp)
|
|
45
|
+
summed = R.dplyr___summarise(grouped, total: E.sum(:value))
|
|
46
|
+
out_path = track(R::Arrow.write_ipc(summed))
|
|
47
|
+
|
|
48
|
+
rows = Galaaz::ArrowIpc.read_batches(out_path)
|
|
49
|
+
by_grp = rows.each_with_object({}) { |r, h| h[r[:grp].to_s] = r[:total].to_f }
|
|
50
|
+
expect(by_grp['a']).to eq(40.0)
|
|
51
|
+
expect(by_grp['b']).to eq(20.5)
|
|
52
|
+
|
|
53
|
+
Galaaz::ArrowIpc.release(out_path)
|
|
54
|
+
expect(File.exist?(out_path)).to be false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'raises when reading a missing IPC file' do
|
|
58
|
+
expect {
|
|
59
|
+
Galaaz::ArrowIpc.read('/tmp/galaaz_missing_b2.arrow')
|
|
60
|
+
}.to raise_error(ArgumentError, /not found/)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'galaaz'
|
|
4
|
+
|
|
5
|
+
describe 'Arrow IPC handoff (Stage B1)' do
|
|
6
|
+
before(:all) do
|
|
7
|
+
skip 'Galaaz::ArrowIpc writer backend not available' unless Galaaz::ArrowIpc.available?
|
|
8
|
+
ok = R::Support.eval("requireNamespace('arrow', quietly=TRUE) && requireNamespace('dplyr', quietly=TRUE)")
|
|
9
|
+
skip 'R packages arrow/dplyr are not available' unless ok == true
|
|
10
|
+
@paths = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
after(:each) do
|
|
14
|
+
Array(@paths).each { |p| Galaaz::ArrowIpc.release(p) }
|
|
15
|
+
@paths = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def track(path)
|
|
19
|
+
@paths << path
|
|
20
|
+
path
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'round-trips a float64 column through open_ipc' do
|
|
24
|
+
values = [1.5, 2.5, -3.0, 0.0]
|
|
25
|
+
path = track(Galaaz::ArrowIpc.write(value: values))
|
|
26
|
+
expect(File.exist?(path)).to be true
|
|
27
|
+
|
|
28
|
+
tbl = R::Arrow.open_ipc(path)
|
|
29
|
+
expect(tbl.rclass.split(' ')).to include('Table')
|
|
30
|
+
expect((R.nrow(tbl) >> 0)).to eq(values.length)
|
|
31
|
+
|
|
32
|
+
collected = R.dplyr___collect(tbl)
|
|
33
|
+
col = collected[['value']]
|
|
34
|
+
got = (1..(col.length >> 0)).map do |i|
|
|
35
|
+
v = col[i]
|
|
36
|
+
v.respond_to?(:>>) ? (v >> 0) : v
|
|
37
|
+
end
|
|
38
|
+
expect(got).to eq(values)
|
|
39
|
+
|
|
40
|
+
Galaaz::ArrowIpc.release(path)
|
|
41
|
+
expect(File.exist?(path)).to be false
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'round-trips multi-column id/grp/value via write_batches' do
|
|
45
|
+
rows = [
|
|
46
|
+
{ id: 1, grp: 'a', value: 10.0 },
|
|
47
|
+
{ id: 2, grp: 'b', value: 20.5 },
|
|
48
|
+
{ id: 3, grp: 'a', value: 30.0 }
|
|
49
|
+
]
|
|
50
|
+
path = track(Galaaz::ArrowIpc.write_batches(rows))
|
|
51
|
+
tbl = R::Arrow.open_ipc(path)
|
|
52
|
+
|
|
53
|
+
expect((R.nrow(tbl) >> 0)).to eq(3)
|
|
54
|
+
summed = R.dplyr___summarise(tbl, total: E.sum(:value))
|
|
55
|
+
out = R.dplyr___collect(summed)
|
|
56
|
+
total = out[['total']]
|
|
57
|
+
tot = total[1]
|
|
58
|
+
tot = tot.respond_to?(:>>) ? (tot >> 0) : tot
|
|
59
|
+
expect(tot).to eq(60.5)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'raises on missing R arrow package path for open_ipc with bad path' do
|
|
63
|
+
expect {
|
|
64
|
+
R::Arrow.open_ipc('/tmp/galaaz_missing_ipc_file_does_not_exist.arrow')
|
|
65
|
+
}.to raise_error(StandardError)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'raises when write is called without a usable backend message path' do
|
|
69
|
+
# available? already gated the suite; still check empty columns
|
|
70
|
+
expect { Galaaz::ArrowIpc.write({}) }.to raise_error(ArgumentError)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'optional timing smoke vs from_ruby_batches for N≈50k' do
|
|
74
|
+
n = 50_000
|
|
75
|
+
cols = {
|
|
76
|
+
id: (0...n).to_a,
|
|
77
|
+
grp: (0...n).map { |i| "g#{i % 5}" },
|
|
78
|
+
value: (0...n).map { |i| (i % 10) + 1.0 }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
82
|
+
path = track(Galaaz::ArrowIpc.write(cols))
|
|
83
|
+
tbl = R::Arrow.open_ipc(path)
|
|
84
|
+
ipc_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000.0
|
|
85
|
+
expect((R.nrow(tbl) >> 0)).to eq(n)
|
|
86
|
+
|
|
87
|
+
rows = (0...n).map { |i| { id: i, grp: "g#{i % 5}", value: (i % 10) + 1.0 } }
|
|
88
|
+
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
89
|
+
tbl2 = R::Arrow.from_ruby_batches(rows)
|
|
90
|
+
batch_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - t1) * 1000.0
|
|
91
|
+
expect((R.nrow(tbl2) >> 0)).to eq(n)
|
|
92
|
+
|
|
93
|
+
# Not a flaky perf gate — only ensure both paths produce a table.
|
|
94
|
+
expect(ipc_ms).to be > 0
|
|
95
|
+
expect(batch_ms).to be > 0
|
|
96
|
+
end
|
|
97
|
+
end
|
data/version.rb
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
$gem_name = "galaaz"
|
|
2
|
-
$version="2.1.
|
|
2
|
+
$version="2.1.2"
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: galaaz
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.1.
|
|
4
|
+
version: 2.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rodrigo Botafogo
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
10
|
+
date: 2026-09-04 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: msgpack
|
|
@@ -87,11 +86,11 @@ description: |
|
|
|
87
86
|
email: rodrigo.a.botafogo@gmail.com
|
|
88
87
|
executables:
|
|
89
88
|
- galaaz
|
|
90
|
-
- gstudio
|
|
91
|
-
- gknit
|
|
92
89
|
- gbookdown
|
|
93
|
-
-
|
|
90
|
+
- gknit
|
|
94
91
|
- gknit-draft
|
|
92
|
+
- grun
|
|
93
|
+
- gstudio
|
|
95
94
|
extensions: []
|
|
96
95
|
extra_rdoc_files: []
|
|
97
96
|
files:
|
|
@@ -337,6 +336,10 @@ files:
|
|
|
337
336
|
- lib/R_interface/runary_operators.rb
|
|
338
337
|
- lib/R_interface/rvector.rb
|
|
339
338
|
- lib/galaaz.rb
|
|
339
|
+
- lib/galaaz/arrow_ipc.rb
|
|
340
|
+
- lib/galaaz/arrow_ipc/java_arrow_backend.rb
|
|
341
|
+
- lib/galaaz/arrow_ipc/red_arrow_backend.rb
|
|
342
|
+
- lib/galaaz/cli.rb
|
|
340
343
|
- lib/galaaz_jruby.rb
|
|
341
344
|
- lib/galaaz_ruby.rb
|
|
342
345
|
- lib/gknit.rb
|
|
@@ -357,6 +360,8 @@ files:
|
|
|
357
360
|
- lib/new_bridge/tcp_framed.rb
|
|
358
361
|
- lib/util/exec_ruby.rb
|
|
359
362
|
- lib/util/inline_file.rb
|
|
363
|
+
- new_bridge_specs/arrow_ipc_async_spec.rb
|
|
364
|
+
- new_bridge_specs/arrow_ipc_export_async_spec.rb
|
|
360
365
|
- new_bridge_specs/benchmark_phase5_5_unboxing_spec.rb
|
|
361
366
|
- new_bridge_specs/eval_r_async_spec.rb
|
|
362
367
|
- new_bridge_specs/integration_phase5_1_concurrent_spec.rb
|
|
@@ -383,10 +388,15 @@ files:
|
|
|
383
388
|
- new_bridge_specs/phase4_2_hardening_spec.rb
|
|
384
389
|
- new_bridge_specs/phase4_3_r_instance_manager_spec.rb
|
|
385
390
|
- new_bridge_specs/phase4_nested_callbacks_spec.rb
|
|
391
|
+
- r_requires/arrow.txt
|
|
386
392
|
- r_requires/ggplot.rb
|
|
393
|
+
- r_requires/knit-extras.txt
|
|
394
|
+
- r_requires/knit.txt
|
|
387
395
|
- r_requires/knitr.rb
|
|
388
396
|
- specs/all.rb
|
|
389
397
|
- specs/arrow_from_ruby_batches_spec.rb
|
|
398
|
+
- specs/arrow_ipc_export_spec.rb
|
|
399
|
+
- specs/arrow_ipc_handoff_spec.rb
|
|
390
400
|
- specs/arrow_semantics_spec.rb
|
|
391
401
|
- specs/bridge_concurrent_spec.rb
|
|
392
402
|
- specs/bridge_nested_spec.rb
|
|
@@ -474,7 +484,6 @@ metadata:
|
|
|
474
484
|
documentation_uri: https://rbotafogo.github.io/galaaz/
|
|
475
485
|
changelog_uri: https://github.com/rbotafogo/galaaz/blob/galaaz2_0/CHANGELOG.md
|
|
476
486
|
yard.run: yri
|
|
477
|
-
post_install_message:
|
|
478
487
|
rdoc_options: []
|
|
479
488
|
require_paths:
|
|
480
489
|
- lib
|
|
@@ -489,8 +498,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
489
498
|
- !ruby/object:Gem::Version
|
|
490
499
|
version: '0'
|
|
491
500
|
requirements: []
|
|
492
|
-
rubygems_version:
|
|
493
|
-
signing_key:
|
|
501
|
+
rubygems_version: 4.0.3
|
|
494
502
|
specification_version: 4
|
|
495
503
|
summary: 'R-on-Rails: tightly couple Ruby and GNU R for data science on the web'
|
|
496
504
|
test_files: []
|