Skip to content

Conversation

@suketa
Copy link
Owner

@suketa suketa commented Nov 1, 2025

refs #976

Summary by CodeRabbit

  • New Features

    • Add class method to create appenders from SQL queries (also exposed via alias from_query).
    • LogicalType constants standardized to uppercase (e.g., BOOLEAN, TINYINT, SMALLINT, INTEGER, BIGINT, etc.) for a more consistent public API.
  • Tests

    • Added tests validating query-based appender creation and basic append/insert behavior.

@coderabbitai
Copy link

coderabbitai bot commented Nov 1, 2025

Walkthrough

Adds a guarded class method DuckDB::Appender.create_query (with alias from_query), documents uppercase DuckDB::LogicalType constants, and introduces a helper to extract rubyDuckDBLogicalType structs; includes tests for the new appender API.

Changes

Cohort / File(s) Summary
CHANGELOG documentation
CHANGELOG.md
Documented new DuckDB::Appender.create_query and updated LogicalType constants to uppercase variants (e.g., DuckDB::LogicalType::BOOLEAN).
Appender create_query feature & tests
ext/duckdb/appender.c, lib/duckdb/appender.rb, test/duckdb_test/appender_test.rb
Added conditional C implementation appender_s_create_query (guarded by HAVE_DUCKDB_H_GE_V1_4_0) that validates inputs, converts Ruby types to DuckDB logical types, prepares name arrays, calls duckdb_appender_create_query, and returns or raises. Exposed as DuckDB::Appender.create_query with alias from_query; tests exercise availability and append/insert behavior.
LogicalType helper
ext/duckdb/logical_type.c, ext/duckdb/logical_type.h
Added rubyDuckDBLogicalType *get_struct_logical_type(VALUE obj) and its header declaration to extract the internal logical-type struct from a Ruby VALUE.

Sequence Diagram(s)

sequenceDiagram
    participant Ruby as Ruby code
    participant Appender as DuckDB::Appender (Ruby)
    participant CExt as C extension (appender.c)
    participant DuckDB as DuckDB lib

    Ruby->>Appender: create_query(con, query, types, table, columns)
    Appender->>CExt: appender_s_create_query(klass, con, query, types, table, columns)

    rect rgba(200,220,255,0.25)
    Note over CExt: Validate inputs (Connection, Array types)\nConvert Ruby type objects to DuckDB logical types\nPrepare table/column name arrays
    end

    CExt->>DuckDB: duckdb_appender_create_query(...)
    alt success
        DuckDB-->>CExt: appender pointer
        CExt->>Ruby: instantiate DuckDB::Appender and return
    else failure
        DuckDB-->>CExt: error
        CExt->>Ruby: raise eDuckDBError
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Areas to inspect closely:
    • ext/duckdb/appender.c: input validation, Ruby→DuckDB type conversions, memory ownership and freeing of name arrays, error handling around duckdb_appender_create_query, and correct use of the version guard (HAVE_DUCKDB_H_GE_V1_4_0).
    • ext/duckdb/logical_type.c / .h: safety of get_struct_logical_type (typed-data extraction) and ABI/visibility implications.
    • Tests (test/duckdb_test/appender_test.rb): conditional skips and real DB interactions correctness.

Possibly related PRs

Poem

🐰 I hopped through C and Ruby glue,

I added queries, constants anew.
A helper fetched types from the heap,
Tests hopped in and woke from sleep,
Patch delivered—quick as a chew!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Add DuckDB::Appender.create_query" is specific, concise, and directly related to the primary change in the pull request. The changeset adds a new public class method create_query to the DuckDB::Appender class, along with supporting implementation in C, test coverage, and an alias method. While the changeset includes secondary changes such as updates to LogicalType constants and helper functions in logical_type.c/h, the title accurately captures the main feature being introduced. The title is clear enough for a teammate scanning commit history to understand the primary purpose of the change.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch appender_create_query

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 835e881 and 10763bc.

📒 Files selected for processing (1)
  • lib/duckdb/appender.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/duckdb/appender.rb
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: All tests passed on Windows

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9fc0f70 and df0ac59.

📒 Files selected for processing (5)
  • CHANGELOG.md (1 hunks)
  • ext/duckdb/appender.c (3 hunks)
  • ext/duckdb/logical_type.c (1 hunks)
  • ext/duckdb/logical_type.h (1 hunks)
  • test/duckdb_test/appender_test.rb (1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: suketa
Repo: suketa/ruby-duckdb PR: 871
File: ext/duckdb/appender.c:170-178
Timestamp: 2025-01-26T00:37:44.001Z
Learning: In ruby-duckdb's DuckDB::Appender#append_uint8, values larger than uint8 max (255) are silently truncated to maintain backward compatibility, following C's implicit truncation behavior. Range checking is intentionally omitted to avoid breaking changes.
📚 Learning: 2025-01-24T22:37:32.786Z
Learnt from: suketa
Repo: suketa/ruby-duckdb PR: 0
File: :0-0
Timestamp: 2025-01-24T22:37:32.786Z
Learning: In Ruby-DuckDB C extensions, when initializing resources like prepared statements, store them directly in the context structure instead of using temporary variables to prevent memory leaks, especially when exceptions might be thrown.

Applied to files:

  • ext/duckdb/logical_type.h
  • ext/duckdb/logical_type.c
  • ext/duckdb/appender.c
📚 Learning: 2025-01-26T00:37:44.001Z
Learnt from: suketa
Repo: suketa/ruby-duckdb PR: 871
File: ext/duckdb/appender.c:170-178
Timestamp: 2025-01-26T00:37:44.001Z
Learning: In ruby-duckdb's DuckDB::Appender#append_uint8, values larger than uint8 max (255) are silently truncated to maintain backward compatibility, following C's implicit truncation behavior. Range checking is intentionally omitted to avoid breaking changes.

Applied to files:

  • test/duckdb_test/appender_test.rb
  • CHANGELOG.md
  • ext/duckdb/appender.c
📚 Learning: 2025-01-24T22:18:24.630Z
Learnt from: suketa
Repo: suketa/ruby-duckdb PR: 0
File: :0-0
Timestamp: 2025-01-24T22:18:24.630Z
Learning: When using ruby-asan (AddressSanitizer), avoid letting DuckDB throw C++ exceptions by checking conditions (like table existence) beforehand and raising Ruby exceptions directly to prevent ASAN from intercepting the C++ exceptions.

Applied to files:

  • ext/duckdb/appender.c
📚 Learning: 2025-01-25T05:20:48.677Z
Learnt from: suketa
Repo: suketa/ruby-duckdb PR: 0
File: :0-0
Timestamp: 2025-01-25T05:20:48.677Z
Learning: In Ruby C extensions, prefer using Ruby's string functions (like rb_str_new2) over C string functions (like strdup) to avoid manual memory management and potential memory leaks.

Applied to files:

  • ext/duckdb/appender.c
📚 Learning: 2025-01-25T05:11:42.406Z
Learnt from: suketa
Repo: suketa/ruby-duckdb PR: 0
File: :0-0
Timestamp: 2025-01-25T05:11:42.406Z
Learning: In Ruby C extensions, when raising exceptions with dynamically allocated error messages, ensure to free the memory after rb_exc_raise as the code continues execution if the exception is caught with rescue.

Applied to files:

  • ext/duckdb/appender.c
🧬 Code graph analysis (3)
ext/duckdb/logical_type.h (1)
ext/duckdb/logical_type.c (1)
  • get_struct_logical_type (44-48)
test/duckdb_test/appender_test.rb (1)
lib/duckdb/connection.rb (1)
  • appender (129-137)
ext/duckdb/appender.c (2)
ext/duckdb/logical_type.c (2)
  • get_struct_logical_type (44-48)
  • allocate (50-53)
ext/duckdb/connection.c (2)
  • get_struct_connection (36-40)
  • allocate (27-30)
🪛 Clang (14.0.6)
ext/duckdb/logical_type.h

[error] 12-12: unknown type name 'VALUE'

(clang-diagnostic-error)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
  • GitHub Check: test (mswin, 1.3.2)
  • GitHub Check: test (3.4.7, 1.4.1)
  • GitHub Check: test (mswin, 1.4.1)
  • GitHub Check: test (3.3.8, 1.3.2)
  • GitHub Check: test (head, 1.3.2)
  • GitHub Check: test (head, 1.4.1)
  • GitHub Check: test (ucrt, 1.3.2)
  • GitHub Check: test (ucrt, 1.4.1)
  • GitHub Check: test (mingw, 1.4.1)
  • GitHub Check: test (mingw, 1.3.2)
  • GitHub Check: test (3.4.1, 1.3.2)
  • GitHub Check: test (3.4.1, 1.4.1)
  • GitHub Check: test (3.2.6, 1.4.1)
  • GitHub Check: test (3.5.0-preview1, 1.3.2)
  • GitHub Check: test (3.2.6, 1.3.2)
  • GitHub Check: test (3.3.8, 1.4.1)
  • GitHub Check: test (3.5.0-preview1, 1.4.1)
  • GitHub Check: test (head, 1.3.2)
  • GitHub Check: test (3.5.0-preview1, 1.3.2)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
ext/duckdb/appender.c (1)

106-121: Prevent buffer over-read when column list is shorter than types

column_count is derived from the types array, but we size column_names using columns.length. If the caller passes fewer column names than logical types, DuckDB will walk past the column_names buffer because the API expects exactly column_count entries, leading to undefined behaviour. Please reject mismatched lengths before calling duckdb_appender_create_query. (duckdb.org)

         idx_t col_count = RARRAY_LEN(columns);
+        if (col_count != column_count) {
+            rb_raise(rb_eArgError,
+                     "column list size (%llu) must match types size (%llu)",
+                     (unsigned long long)col_count,
+                     (unsigned long long)column_count);
+        }
         column_names = ALLOCA_N(const char *, (size_t)col_count);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df0ac59 and 835e881.

📒 Files selected for processing (3)
  • ext/duckdb/appender.c (3 hunks)
  • lib/duckdb/appender.rb (1 hunks)
  • test/duckdb_test/appender_test.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/duckdb_test/appender_test.rb
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: suketa
Repo: suketa/ruby-duckdb PR: 871
File: ext/duckdb/appender.c:170-178
Timestamp: 2025-01-26T00:37:44.001Z
Learning: In ruby-duckdb's DuckDB::Appender#append_uint8, values larger than uint8 max (255) are silently truncated to maintain backward compatibility, following C's implicit truncation behavior. Range checking is intentionally omitted to avoid breaking changes.
Learnt from: suketa
Repo: suketa/ruby-duckdb PR: 0
File: :0-0
Timestamp: 2025-01-24T22:37:32.786Z
Learning: In Ruby-DuckDB C extensions, when initializing resources like prepared statements, store them directly in the context structure instead of using temporary variables to prevent memory leaks, especially when exceptions might be thrown.
📚 Learning: 2025-01-24T22:37:32.786Z
Learnt from: suketa
Repo: suketa/ruby-duckdb PR: 0
File: :0-0
Timestamp: 2025-01-24T22:37:32.786Z
Learning: In Ruby-DuckDB C extensions, when initializing resources like prepared statements, store them directly in the context structure instead of using temporary variables to prevent memory leaks, especially when exceptions might be thrown.

Applied to files:

  • ext/duckdb/appender.c
📚 Learning: 2025-01-24T22:18:24.630Z
Learnt from: suketa
Repo: suketa/ruby-duckdb PR: 0
File: :0-0
Timestamp: 2025-01-24T22:18:24.630Z
Learning: When using ruby-asan (AddressSanitizer), avoid letting DuckDB throw C++ exceptions by checking conditions (like table existence) beforehand and raising Ruby exceptions directly to prevent ASAN from intercepting the C++ exceptions.

Applied to files:

  • ext/duckdb/appender.c
📚 Learning: 2025-01-26T00:37:44.001Z
Learnt from: suketa
Repo: suketa/ruby-duckdb PR: 871
File: ext/duckdb/appender.c:170-178
Timestamp: 2025-01-26T00:37:44.001Z
Learning: In ruby-duckdb's DuckDB::Appender#append_uint8, values larger than uint8 max (255) are silently truncated to maintain backward compatibility, following C's implicit truncation behavior. Range checking is intentionally omitted to avoid breaking changes.

Applied to files:

  • ext/duckdb/appender.c
📚 Learning: 2025-01-25T05:20:48.677Z
Learnt from: suketa
Repo: suketa/ruby-duckdb PR: 0
File: :0-0
Timestamp: 2025-01-25T05:20:48.677Z
Learning: In Ruby C extensions, prefer using Ruby's string functions (like rb_str_new2) over C string functions (like strdup) to avoid manual memory management and potential memory leaks.

Applied to files:

  • ext/duckdb/appender.c
📚 Learning: 2025-01-25T05:11:42.406Z
Learnt from: suketa
Repo: suketa/ruby-duckdb PR: 0
File: :0-0
Timestamp: 2025-01-25T05:11:42.406Z
Learning: In Ruby C extensions, when raising exceptions with dynamically allocated error messages, ensure to free the memory after rb_exc_raise as the code continues execution if the exception is caught with rescue.

Applied to files:

  • ext/duckdb/appender.c
🧬 Code graph analysis (1)
ext/duckdb/appender.c (2)
ext/duckdb/logical_type.c (1)
  • get_struct_logical_type (44-48)
ext/duckdb/connection.c (1)
  • get_struct_connection (36-40)
🪛 GitHub Actions: MacOS
lib/duckdb/appender.rb

[error] 27-27: undefined method 'create_query' for class '#Class:DuckDB::Appender' (NameError)

🪛 GitHub Actions: Ubuntu
lib/duckdb/appender.rb

[error] 27-27: NameError: undefined method `create_query' for class DuckDB::Appender. This occurs during singleton class definition at line 27.

🪛 GitHub Actions: Windows
lib/duckdb/appender.rb

[error] 27-27: undefined method 'create_query' for class '#Class:DuckDB::Appender' (NameError). The rake test command failed with exit code 1.

Comment on lines 26 to 28
class << self
alias from_query create_query
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Guard alias against missing create_query

create_query is only defined when the native extension is built against DuckDB ≥ 1.4.0, but the unconditional alias runs for all builds. On older libraries this raises NameError during class definition (see failing CI jobs). Wrap the alias in a guard or set it up from the C side under the same feature flag so the file loads cleanly when the API is unavailable.

-    class << self
-      alias from_query create_query
-    end
+    class << self
+      if singleton_class.method_defined?(:create_query)
+        alias_method :from_query, :create_query
+      end
+    end

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 GitHub Actions: MacOS

[error] 27-27: undefined method 'create_query' for class '#Class:DuckDB::Appender' (NameError)

🪛 GitHub Actions: Ubuntu

[error] 27-27: NameError: undefined method `create_query' for class DuckDB::Appender. This occurs during singleton class definition at line 27.

🪛 GitHub Actions: Windows

[error] 27-27: undefined method 'create_query' for class '#Class:DuckDB::Appender' (NameError). The rake test command failed with exit code 1.

🤖 Prompt for AI Agents
In lib/duckdb/appender.rb around lines 26–28, the unconditional alias from_query
= create_query fails on builds where create_query isn't defined (DuckDB <
1.4.0); guard the alias so the file loads on older libraries. Replace the bare
alias with a conditional that checks whether the singleton responds
to/create_query is defined (e.g., via respond_to?(:create_query) or
singleton_class.method_defined?) and only performs the alias when present;
alternatively implement the alias on the C side under the same feature flag so
the Ruby file never needs the guard.

@suketa suketa merged commit 492f661 into main Nov 1, 2025
38 checks passed
@suketa suketa deleted the appender_create_query branch November 1, 2025 09:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants