Skip to content

Commit a894f62

Browse files
committed
feat: Implement dynamic loading of libchdb.so via dlopen and auto-download during gem install
1 parent d6087cf commit a894f62

12 files changed

+185
-66
lines changed

.rubocop.yml

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ AllCops:
44
- 'spec/**/*'
55
- 'tmp/**/*'
66
- 'ext/**/extconf.rb'
7+
- 'pkg/**/*'
78

89
NewCops: enable
910

chdb.gemspec

+13-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@ Gem::Specification.new do |s|
3838
'INSTALLATION.md',
3939
'LICENSE',
4040
'README.md',
41+
'dependencies.yml',
42+
'ext/chdb/chdb_handle.c',
43+
'ext/chdb/chdb_handle.h',
44+
'ext/chdb/chdb.c',
45+
'ext/chdb/connection.c',
46+
'ext/chdb/connection.h',
47+
'ext/chdb/constants.h',
48+
'ext/chdb/exception.c',
49+
'ext/chdb/exception.h',
50+
'ext/chdb/extconf.rb',
51+
'ext/chdb/local_result.c',
52+
'ext/chdb/local_result.h',
4153
'lib/chdb.rb',
4254
'lib/chdb/constants.rb',
4355
'lib/chdb/data_path.rb',
@@ -57,5 +69,5 @@ Gem::Specification.new do |s|
5769

5870
s.add_dependency 'csv', '~> 3.1'
5971

60-
# s.extensions << 'ext/chdb/extconf.rb'
72+
s.extensions << 'ext/chdb/extconf.rb'
6173
end

ext/chdb/chdb.c

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <ruby.h>
22

33
#include "chdb.h"
4+
#include "chdb_handle.h"
45
#include "constants.h"
56
#include "connection.h"
67
#include "exception.h"
@@ -22,6 +23,7 @@ void Init_chdb_native()
2223
DEBUG_PRINT("Initializing chdb extension");
2324

2425
init_exception();
26+
init_chdb_handle();
2527
init_chdb_constants();
2628
init_local_result();
2729
init_connection();

ext/chdb/chdb_handle.c

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#include "chdb_handle.h"
2+
3+
#include <dlfcn.h>
4+
#include <ruby.h>
5+
6+
#include "exception.h"
7+
8+
void *chdb_handle = NULL;
9+
connect_chdb_func connect_chdb_ptr = NULL;
10+
close_conn_func close_conn_ptr = NULL;
11+
query_conn_func query_conn_ptr = NULL;
12+
13+
VALUE get_chdb_rb_path(void)
14+
{
15+
VALUE chdb_module = rb_const_get(rb_cObject, rb_intern("ChDB"));
16+
return rb_funcall(chdb_module, rb_intern("lib_file_path"), 0);
17+
}
18+
19+
void init_chdb_handle()
20+
{
21+
VALUE rb_path = get_chdb_rb_path();
22+
VALUE lib_dir = rb_file_dirname(rb_file_dirname(rb_path));
23+
VALUE lib_path = rb_str_cat2(lib_dir, "/lib/chdb/lib/libchdb.so");
24+
// printf("chdb.rb path from Ruby: %s\n", StringValueCStr(lib_path));
25+
26+
connect_chdb_ptr = NULL;
27+
close_conn_ptr = NULL;
28+
query_conn_ptr = NULL;
29+
30+
chdb_handle = dlopen(RSTRING_PTR(lib_path), RTLD_LAZY | RTLD_GLOBAL);
31+
if (!chdb_handle)
32+
{
33+
rb_raise(cChDBError, "Failed to load chdb library: %s\nCheck if libchdb.so exists at: %s",
34+
dlerror(), RSTRING_PTR(lib_path));
35+
}
36+
37+
connect_chdb_ptr = (connect_chdb_func)dlsym(chdb_handle, "connect_chdb");
38+
close_conn_ptr = (close_conn_func)dlsym(chdb_handle, "close_conn");
39+
query_conn_ptr = (query_conn_func)dlsym(chdb_handle, "query_conn");
40+
41+
if (!connect_chdb_ptr || !close_conn_ptr || !query_conn_ptr)
42+
{
43+
rb_raise(cChDBError, "Symbol loading failed: %s\nMissing functions: connect_chdb(%p) close_conn(%p) query_conn(%p)",
44+
dlerror(),
45+
(void*)connect_chdb_ptr,
46+
(void*)close_conn_ptr,
47+
(void*)query_conn_ptr);
48+
}
49+
}

ext/chdb/chdb_handle.h

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#ifndef CHDB_HANDLE_H
2+
#define CHDB_HANDLE_H
3+
4+
typedef struct chdb_conn **(*connect_chdb_func)(int, char**);
5+
typedef void (*close_conn_func)(struct chdb_conn**);
6+
typedef struct local_result_v2 *(*query_conn_func)(struct chdb_conn*, const char*, const char*);
7+
8+
extern connect_chdb_func connect_chdb_ptr;
9+
extern close_conn_func close_conn_ptr;
10+
extern query_conn_func query_conn_ptr;
11+
12+
extern void *chdb_handle;
13+
14+
void init_chdb_handle();
15+
16+
#endif

ext/chdb/connection.c

+22-22
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
11
#include "connection.h"
22

3+
#include "chdb_handle.h"
34
#include "constants.h"
45
#include "exception.h"
56
#include "include/chdb.h"
67
#include "local_result.h"
78

9+
static void connection_free(void *ptr)
10+
{
11+
Connection *conn = (Connection *)ptr;
12+
DEBUG_PRINT("Closing connection: %p", (void*)conn->c_conn);
13+
if (conn->c_conn)
14+
{
15+
close_conn_ptr(conn->c_conn);
16+
conn->c_conn = NULL;
17+
}
18+
free(conn);
19+
}
20+
21+
const rb_data_type_t ConnectionType =
22+
{
23+
"Connection",
24+
{NULL, connection_free, NULL},
25+
};
26+
827
void init_connection()
928
{
1029
VALUE mChDB = rb_define_module("ChDB");
@@ -39,7 +58,7 @@ VALUE connection_initialize(VALUE self, VALUE argc, VALUE argv)
3958

4059
Connection *conn;
4160
TypedData_Get_Struct(self, Connection, &ConnectionType, conn);
42-
conn->c_conn = connect_chdb(c_argc, c_argv);
61+
conn->c_conn = connect_chdb_ptr(c_argc, c_argv);
4362

4463
if (!conn->c_conn)
4564
{
@@ -60,7 +79,7 @@ VALUE connection_query(VALUE self, VALUE query, VALUE format)
6079
Check_Type(query, T_STRING);
6180
Check_Type(format, T_STRING);
6281

63-
struct local_result_v2 *c_result = query_conn(
82+
struct local_result_v2 *c_result = query_conn_ptr(
6483
*conn->c_conn,
6584
StringValueCStr(query),
6685
StringValueCStr(format)
@@ -92,27 +111,8 @@ VALUE connection_close(VALUE self)
92111

93112
if (conn->c_conn)
94113
{
95-
close_conn(conn->c_conn);
114+
close_conn_ptr(conn->c_conn);
96115
conn->c_conn = NULL;
97116
}
98117
return Qnil;
99118
}
100-
101-
static void connection_free(void *ptr)
102-
{
103-
Connection *conn = (Connection *)ptr;
104-
DEBUG_PRINT("Closing connection: %p", (void*)conn->c_conn);
105-
if (conn->c_conn)
106-
{
107-
close_conn(conn->c_conn);
108-
}
109-
free(conn);
110-
}
111-
112-
const rb_data_type_t ConnectionType =
113-
{
114-
"Connection",
115-
{NULL, connection_free, NULL},
116-
};
117-
118-
// 其他 Connection 方法保持不变...

ext/chdb/extconf.rb

+70-35
Original file line numberDiff line numberDiff line change
@@ -18,34 +18,39 @@ def configure
1818
create_makefile('chdb/chdb_native')
1919
end
2020

21+
def compiled?
22+
return false if cross_build?
23+
24+
major_version = RUBY_VERSION.match(/(\d+\.\d+)/)[1]
25+
version_dir = File.join(package_root_dir, 'lib', 'chdb', major_version)
26+
27+
extension = if determine_target_platform.include?('darwin')
28+
'bundle'
29+
else
30+
'so'
31+
end
32+
lib_file = "#{libname}.#{extension}"
33+
34+
File.exist?(File.join(version_dir, lib_file))
35+
end
36+
2137
def configure_cross_compiler
2238
RbConfig::CONFIG['CC'] = RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
2339
ENV['CC'] = RbConfig::CONFIG['CC']
2440
end
2541

42+
def cross_build?
43+
enable_config('cross-build')
44+
end
45+
2646
def libname
27-
'chdb'
47+
'chdb_native'
2848
end
2949

3050
def configure_extension
3151
include_path = File.expand_path('ext/chdb/include', package_root_dir)
3252
append_cppflags("-I#{include_path}")
33-
34-
lib_path = File.expand_path('ext/chdb/lib', package_root_dir)
35-
append_ldflags("-L#{lib_path}")
36-
37-
target_platform = determine_target_platform
38-
if target_platform.include?('darwin')
39-
append_ldflags('-Wl,-rpath,@loader_path/../lib')
40-
else
41-
append_ldflags("-Wl,-rpath,'$$ORIGIN/../lib'")
42-
end
43-
4453
abort_could_not_find('chdb.h') unless find_header('chdb.h', include_path)
45-
46-
return if find_library(libname, nil, lib_path)
47-
48-
abort_could_not_find(libname)
4954
end
5055

5156
def abort_could_not_find(missing)
@@ -60,9 +65,25 @@ def download_and_extract
6065
target_platform = determine_target_platform
6166
version = fetch_chdb_version
6267
download_dir = determine_download_directory(target_platform, version)
63-
64-
unless Dir.exist?(download_dir)
68+
need_download = false
69+
70+
if Dir.exist?(download_dir)
71+
required_files = [
72+
File.join(download_dir, 'chdb.h'),
73+
File.join(download_dir, 'libchdb.so')
74+
]
75+
76+
need_download = !required_files.all? { |f| File.exist?(f) }
77+
if need_download
78+
puts 'Missing required files, cleaning download directory...'
79+
FileUtils.rm_rf(Dir.glob("#{download_dir}/*"))
80+
end
81+
else
6582
FileUtils.mkdir_p(download_dir)
83+
need_download = true
84+
end
85+
86+
if need_download
6687
file_name = get_file_name(target_platform)
6788
url = build_download_url(version, file_name)
6889
download_tarball(url, download_dir, file_name)
@@ -83,7 +104,7 @@ def determine_target_platform
83104
when /arm64-darwin/ then 'arm64-darwin'
84105
when /x86_64-darwin/ then 'x86_64-darwin'
85106
else
86-
'unknown-platform'
107+
raise ArgumentError, "Unsupported platform: #{RUBY_PLATFORM}."
87108
end
88109
end
89110

@@ -114,8 +135,19 @@ def download_tarball(url, download_dir, file_name)
114135
tarball = File.join(download_dir, file_name)
115136
puts "Downloading chdb library for #{determine_target_platform}..."
116137

117-
URI.open(url) do |remote| # rubocop:disable Security/Open
118-
IO.copy_stream(remote, tarball)
138+
max_retries = 3
139+
retries = 0
140+
141+
begin
142+
URI.open(url) do |remote| # rubocop:disable Security/Open
143+
IO.copy_stream(remote, tarball)
144+
end
145+
rescue StandardError => e
146+
raise "Failed to download after #{max_retries} attempts: #{e.message}" unless retries < max_retries
147+
148+
retries += 1
149+
puts "Download failed: #{e.message}. Retrying (attempt #{retries}/#{max_retries})..."
150+
retry
119151
end
120152
end
121153

@@ -125,24 +157,27 @@ def extract_tarball(download_dir, file_name)
125157
end
126158

127159
def copy_files(download_dir, _version)
128-
ext_chdb_path = File.join(package_root_dir, 'ext/chdb')
129-
[%w[*.h], %w[*.so], %w[*.dylib]].each do |(glob_pattern)|
130-
src_dir, pattern = File.split(glob_pattern)
131-
160+
[%w[*.h], %w[*.so]].each do |(glob_pattern)|
161+
# Removed the unused variable src_dir
162+
pattern = File.basename(glob_pattern)
132163
dest_subdir = case pattern
133164
when '*.h' then 'include'
134165
else 'lib'
135166
end
136-
src = File.join(download_dir, src_dir, pattern)
137-
dest = File.join(ext_chdb_path, dest_subdir)
138-
FileUtils.mkdir_p(dest)
139-
FileUtils.cp_r(Dir.glob(src), dest, remove_destination: true)
140-
141-
# target_platform = determine_target_platform
142-
# if target_platform.include?('darwin') && (pattern == '*.so')
143-
# system("install_name_tool -id '@rpath/libchdb.so' #{File.join(dest, 'libchdb.so')}")
144-
# system("codesign -f -s - #{File.join(dest, 'libchdb.so')}")
145-
# end
167+
dest_dir = File.join(package_root_dir, 'ext/chdb', dest_subdir)
168+
src_files = Dir.glob(File.join(download_dir, pattern))
169+
170+
extra_dirs = []
171+
extra_dirs << File.join(package_root_dir, 'lib/chdb/lib') if pattern == '*.so'
172+
173+
([dest_dir] + extra_dirs).each do |dest|
174+
FileUtils.mkdir_p(dest)
175+
176+
src_files.each do |src_file|
177+
dest_file = File.join(dest, File.basename(src_file))
178+
FileUtils.ln_s(File.expand_path(src_file), dest_file, force: true)
179+
end
180+
end
146181
end
147182
end
148183

ext/chdb/local_result.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ VALUE local_result_rows_read(VALUE self);
2323

2424
VALUE local_result_bytes_read(VALUE self);
2525

26-
#endif
26+
#endif

lib/chdb.rb

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# frozen_string_literal: true
22

3+
module ChDB
4+
def self.lib_file_path
5+
@lib_file_path ||= File.expand_path(__FILE__)
6+
end
7+
end
8+
39
begin
410
RUBY_VERSION =~ /(\d+\.\d+)/
511
require "chdb/#{Regexp.last_match(1)}/chdb_native"

lib/chdb/version.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
module ChDB
44
# (String) the version of the chdb gem, e.g. "0.1.0"
5-
VERSION = '0.1.0.rc.1'
5+
VERSION = '0.1.0.rc.2'
66
end

rakelib/check_manifest.rake

+1-3
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,15 @@ task :check_manifest do # rubocop:disable Metrics/BlockLength
3333
.rspec_status
3434
.ruby-version
3535
.rubocop.yml
36-
dependencies.yml
37-
ext/chdb/*.{c,h}
3836
lib/chdb/chdb*.{bundle,so}
37+
ext/chdb/include/chdb.h
3938
Gemfile*
4039
Rakefile
4140
[a-z]*.{log,out}
4241
[0-9]*
4342
*.gemspec
4443
*.so
4544
CHANGELOG.md
46-
ext/chdb/extconf.rb
4745
]
4846

4947
intended_directories = Dir.children('.')

0 commit comments

Comments
 (0)