diff --git a/.github/workflows/ruby_asan_on_ubuntu.yml b/.github/workflows/ruby_asan_on_ubuntu.yml new file mode 100644 index 00000000..12652c51 --- /dev/null +++ b/.github/workflows/ruby_asan_on_ubuntu.yml @@ -0,0 +1,71 @@ +name: Ubuntu + +on: + push: + branches: + - main + pull_request: + types: + - opened + - synchronize + - reopened + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + ruby: ['asan'] + duckdb: ['1.1.3', '1.1.1', '1.2.0'] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + + - name: duckdb cache + id: duckdb-cache + uses: actions/cache@v4 + with: + path: duckdb-v${{ matrix.duckdb }} + key: ${{ runner.os }}-duckdb-v${{ matrix.duckdb }} + + - name: Build duckdb ${{ matrix.duckdb }} + env: + DUCKDB_VERSION: ${{ matrix.duckdb }} + if: steps.duckdb-cache.outputs.cache-hit != 'true' + run: | + git clone -b v$DUCKDB_VERSION https://github.com/cwida/duckdb.git duckdb-tmp-v$DUCKDB_VERSION + cd duckdb-tmp-v$DUCKDB_VERSION && make && cd .. + rm -rf duckdb-v$DUCKDB_VERSION + mkdir -p duckdb-v$DUCKDB_VERSION/build/release/src duckdb-v$DUCKDB_VERSION/src + cp -rip duckdb-tmp-v$DUCKDB_VERSION/build/release/src/*.so duckdb-v$DUCKDB_VERSION/build/release/src + cp -rip duckdb-tmp-v$DUCKDB_VERSION/src/include duckdb-v$DUCKDB_VERSION/src/ + + - name: bundle install with Ruby ${{ matrix.ruby }} + env: + DUCKDB_VERSION: ${{ matrix.duckdb }} + run: | + bundle install --jobs 4 --retry 3 + + - name: Build test with DUCKDB_API_NO_DEPRECATED and Ruby ${{ matrix.ruby }} + env: + DUCKDB_VERSION: ${{ matrix.duckdb }} + run: | + env DUCKDB_API_NO_DEPRECATED=1 bundle exec rake build -- --with-duckdb-include=${GITHUB_WORKSPACE}/duckdb-v${DUCKDB_VERSION}/src/include --with-duckdb-lib=${GITHUB_WORKSPACE}/duckdb-v${DUCKDB_VERSION}/build/release/src/ + bundle exec rake clean + + - name: Build with Ruby ${{ matrix.ruby }} + env: + DUCKDB_VERSION: ${{ matrix.duckdb }} + run: | + bundle exec rake build -- --with-duckdb-include=${GITHUB_WORKSPACE}/duckdb-v${DUCKDB_VERSION}/src/include --with-duckdb-lib=${GITHUB_WORKSPACE}/duckdb-v${DUCKDB_VERSION}/build/release/src/ + + - name: test with Ruby ${{ matrix.ruby }} + env: + DUCKDB_VERSION: ${{ matrix.duckdb }} + run: | + env RUBYOPT=-W:deprecated ruby -Ilib test/duckdb_test/ruby_asan_test.rb diff --git a/ext/duckdb/prepared_statement.c b/ext/duckdb/prepared_statement.c index e8ca553d..fbeab38a 100644 --- a/ext/duckdb/prepared_statement.c +++ b/ext/duckdb/prepared_statement.c @@ -81,6 +81,10 @@ VALUE rbduckdb_prepared_statement_new(duckdb_connection con, duckdb_extracted_st static VALUE duckdb_prepared_statement_initialize(VALUE self, VALUE con, VALUE query) { rubyDuckDBConnection *ctxcon; rubyDuckDBPreparedStatement *ctx; + duckdb_state state; + const char *error; + char *pquery; + VALUE msg; if (!rb_obj_is_kind_of(con, cDuckDBConnection)) { rb_raise(rb_eTypeError, "1st argument should be instance of DackDB::Connection"); @@ -89,10 +93,16 @@ static VALUE duckdb_prepared_statement_initialize(VALUE self, VALUE con, VALUE q TypedData_Get_Struct(self, rubyDuckDBPreparedStatement, &prepared_statement_data_type, ctx); ctxcon = get_struct_connection(con); - if (duckdb_prepare(ctxcon->con, StringValuePtr(query), &(ctx->prepared_statement)) == DuckDBError) { - const char *error = duckdb_prepare_error(ctx->prepared_statement); - rb_raise(eDuckDBError, "%s", error ? error : "Failed to prepare statement(Database connection closed?)."); + pquery = StringValueCStr(query); + state = duckdb_prepare(ctxcon->con, pquery, &(ctx->prepared_statement)); + + // If preparation failed, raise Ruby exception with the error message + if (state == DuckDBError) { + error = duckdb_prepare_error(ctx->prepared_statement); + msg = rb_str_new2(error ? error : "Failed to execute prepared statement."); + rb_raise(eDuckDBError, "%s", StringValueCStr(msg)); } + return self; } diff --git a/test/duckdb_test/ruby_asan_test.rb b/test/duckdb_test/ruby_asan_test.rb new file mode 100644 index 00000000..891c47f6 --- /dev/null +++ b/test/duckdb_test/ruby_asan_test.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true +require 'duckdb' + +def run_duckdb_asan_test + db = DuckDB::Database.open + con = db.connect + con.query('CREATE TABLE test (a INTEGER, b VARCHAR)') + DuckDB::PreparedStatement.new(con, 'INSERT INTO test VALUES (?, ?)') + # DuckDB::PreparedStatement.new(con, 'INSERT INTO test VALUES (?, "hello")') +rescue StandardError => e + p e +end + +run_duckdb_asan_test