Skip to content

Add TIME_NS column support to DuckDB::Result#1237

Merged
suketa merged 3 commits intomainfrom
support-time-ns
Apr 4, 2026
Merged

Add TIME_NS column support to DuckDB::Result#1237
suketa merged 3 commits intomainfrom
support-time-ns

Conversation

@suketa
Copy link
Copy Markdown
Owner

@suketa suketa commented Apr 4, 2026

Summary

Add support for TIME_NS (DUCKDB_TYPE_TIME_NS, type 39) columns in DuckDB::Result.

TIME_NS values are returned as Time objects. Nanoseconds are truncated to microseconds, consistent with how TIMESTAMP_NS is handled and with Ruby's Time precision limit.

Changes

  • lib/duckdb/converter.rb — add _to_time_from_duckdb_time_ns(nanos) that decomposes nanoseconds-since-midnight into hour/min/sec/microsecond
  • ext/duckdb/conveter.c / converter.h — add rbduckdb_time_ns_to_ruby C function and id__to_time_from_duckdb_time_ns ID
  • ext/duckdb/result.c — add vector_time_ns and DUCKDB_TYPE_TIME_NS case in rbduckdb_vector_value_at
  • ext/duckdb/value_impl.c — add DUCKDB_TYPE_TIME_NS case using duckdb_get_time_ns
  • test/duckdb_test/result_each_test.rb — add TIME_NS entry to TEST_TABLES (covers both sync and async query paths)
  • CHANGELOG.md — document the addition

Summary by CodeRabbit

  • New Features
    • Support for TIME_NS columns in DuckDB results: returned as Ruby Time objects with nanoseconds truncated to microseconds.
  • Tests
    • Added tests for TIME_NS handling and per-runtime version checks that skip tests when the linked DuckDB is older than required.
  • Documentation
    • Updated changelog with the new TIME_NS entry.

- Add `_to_time_from_duckdb_time_ns` in converter.rb to decompose
  nanoseconds-since-midnight into hour/min/sec/microsecond (sub-microsecond
  precision is truncated to match Ruby's Time precision)
- Add `rbduckdb_time_ns_to_ruby` C function and `id__to_time_from_duckdb_time_ns`
  ID wiring in conveter.c / converter.h
- Handle `DUCKDB_TYPE_TIME_NS` (type 39) in result.c vector dispatch and
  value_impl.c duckdb_value dispatch
- Add focused test file result_time_ns_test.rb
- Add TIME_NS entry to result_each_test.rb TEST_TABLES

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 51848376-3afb-4e21-a8d2-20e6d771477d

📥 Commits

Reviewing files that changed from the base of the PR and between 9a0784f and 8feddfe.

📒 Files selected for processing (1)
  • test/duckdb_test/result_each_test.rb
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/duckdb_test/result_each_test.rb

📝 Walkthrough

Walkthrough

Adds DuckDB TIME_NS support across the Ruby extension: new C converter ID/function, vector/value dispatch for DUCKDB_TYPE_TIME_NS, a Ruby converter method that truncates nanoseconds to microseconds, changelog entry, and tests with DuckDB version gating.

Changes

Cohort / File(s) Summary
C Converter Infrastructure
ext/duckdb/converter.h, ext/duckdb/conveter.c
Introduce id__to_time_from_duckdb_time_ns and rbduckdb_time_ns_to_ruby(duckdb_time_ns) to expose TIME_NS conversion from the C extension to Ruby.
Type Dispatch Integration
ext/duckdb/result.c, ext/duckdb/value_impl.c
Handle DUCKDB_TYPE_TIME_NS in vector and value conversion paths, calling the new TIME_NS converter helper.
Ruby Converter Logic
lib/duckdb/converter.rb
Add DuckDB::Converter._to_time_from_duckdb_time_ns(nanos) to decompose nanoseconds and delegate to existing time constructor (nanoseconds truncated to microseconds).
Tests & Changelog
test/duckdb_test/result_each_test.rb, CHANGELOG.md
Add TIME_NS test cases with minimum DuckDB version gating (1.5.0) and a changelog entry describing TIME_NS support and microsecond truncation.

Sequence Diagram(s)

sequenceDiagram
    participant RubyApp as Ruby (caller)
    participant Ext as C extension
    participant DuckDB as DuckDB C API
    participant Converter as Ruby Converter

    RubyApp->>DuckDB: execute query with TIME_NS column
    DuckDB-->>Ext: returns vector with DUCKDB_TYPE_TIME_NS elements
    Ext->>Ext: rbduckdb_vector_value_at detects DUCKDB_TYPE_TIME_NS
    Ext->>Converter: call rbduckdb_time_ns_to_ruby(ts.nanos)
    Converter-->>RubyApp: returns Ruby Time (nanos truncated to micros)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hopped through nanos, quick and spry,

From DuckDB rows up to Ruby sky,
I trimmed the bits to micro delight,
Now Time ticks gentle through the night. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 TIME_NS column support to DuckDB::Result' accurately and concisely describes the main change: adding support for TIME_NS columns to DuckDB::Result, which is the primary objective of the entire pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch support-time-ns

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
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/duckdb_test/result_each_test.rb`:
- Line 110: The TIME_NS test row is inserting the literal "'12:34:56.789123456'"
without an explicit type cast, causing DuckDB to return NULL and later a
NoMethodError when Time methods are called; update the test data row where the
column/type is 'TIME_NS' to cast the literal to TIME_NS (change the literal to
include ::TIME_NS) so the INSERT parses the nanosecond-precision time
correctly—look for the array entry containing 'TIME_NS' in result_each_test.rb
and modify the third value in that tuple to "'12:34:56.789123456'::TIME_NS".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 158af264-fd15-41b9-88c4-4e7c8e00e1b6

📥 Commits

Reviewing files that changed from the base of the PR and between bdf9b39 and 1903178.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • ext/duckdb/converter.h
  • ext/duckdb/conveter.c
  • ext/duckdb/result.c
  • ext/duckdb/value_impl.c
  • lib/duckdb/converter.rb
  • test/duckdb_test/result_each_test.rb

# set TIMEZONE to Asia/Kabul to test TIMETZ and TIMESTAMPTZ
[:ok, 'TIMETZ', 'TIMETZ', "'2019-11-03 12:34:56.123456789'", Time, timetz_expected ],
[:ok, 'TIMESTAMPTZ', 'TIMESTAMPTZ', "'2019-11-03 12:34:56.123456789'", Time, Time.parse('2019-11-03 08:04:56.123456+0000') ],
[:ok, 'TIME_NS', 'TIME_NS', "'12:34:56.789123456'", Time, Time.parse('12:34:56.789123') ],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if TIME_NS is a recognized DuckDB type and how it should be used
rg -n 'TIME_NS' --type=c ext/duckdb/

# Check similar test patterns for other time types
rg -n "TIMESTAMP_NS|TIME_TZ" test/duckdb_test/result_each_test.rb -A2 -B2

# Look for how TIME_NS is documented/used elsewhere
rg -n 'time_ns|TIME_NS' lib/ ext/

Repository: suketa/ruby-duckdb

Length of output: 2652


🏁 Script executed:

sed -n '70,90p' lib/duckdb/converter.rb

Repository: suketa/ruby-duckdb

Length of output: 777


🏁 Script executed:

sed -n '130,145p' ext/duckdb/conveter.c

Repository: suketa/ruby-duckdb

Length of output: 676


🏁 Script executed:

grep -n "time_ns\|TIME_NS" lib/duckdb/converter.rb -B2 -A5

Repository: suketa/ruby-duckdb

Length of output: 309


🏁 Script executed:

grep -n "_to_time_from_duckdb_time" lib/duckdb/converter.rb -A3

Repository: suketa/ruby-duckdb

Length of output: 1621


🏁 Script executed:

sed -n '140,150p' test/duckdb_test/result_each_test.rb

Repository: suketa/ruby-duckdb

Length of output: 476


🏁 Script executed:

grep -n "TIME_NS\|time_ns" ext/extconf.rb ext/duckdb.h 2>/dev/null || grep -rn "DuckDB.*1\." ext/extconf.rb 2>/dev/null | head -20

Repository: suketa/ruby-duckdb

Length of output: 44


🏁 Script executed:

# Check if there are any version checks or conditionals around TIME_NS
grep -rn "TIME_NS" ext/duckdb/ --type=c -B3 -A3 | head -40

# Check DuckDB C library version constants
fd -e h -e c ext/duckdb | xargs grep -l "DUCKDB_VERSION\|DUCKDB_TYPE_TIME"

Repository: suketa/ruby-duckdb

Length of output: 529


🏁 Script executed:

# Check if there's a .duckdb.so or compiled extension
find ext -name "*.so" -o -name "*.h" | grep -E "(version|duckdb\.h)" | head -5

# Search for any version-gated code or TIME_NS conditionals
rg "VERSION|TIME_NS" ext/ --type=c --type=h -i | grep -E "(if|define)" | head -20

Repository: suketa/ruby-duckdb

Length of output: 195


🏁 Script executed:

# Check DuckDB header for type definitions
cat ext/duckdb/ruby-duckdb.h | grep -i "time_ns\|version" | head -20

# Look for the actual duckdb version being used
rg "duckdb.*version" ext/ lib/ -i -A2 -B2

# Check when TIME_NS was introduced in DuckDB - look at comments or documentation
rg "TIME_NS|type.*39" ext/duckdb/ -A2 -B2

Repository: suketa/ruby-duckdb

Length of output: 3946


🏁 Script executed:

# Find the query generation logic in the test
sed -n '1,50p' test/duckdb_test/result_each_test.rb

# Look for the query building logic
rg -n "insert into\|CREATE TABLE\|def.*query" test/duckdb_test/result_each_test.rb -i -B2 -A2 | head -40

Repository: suketa/ruby-duckdb

Length of output: 4720


🏁 Script executed:

# Check git log for the TIME_NS addition
git log -p --all -- test/duckdb_test/result_each_test.rb | grep -A5 -B5 "TIME_NS" | head -50

Repository: suketa/ruby-duckdb

Length of output: 44


🏁 Script executed:

# Find the exact TIME_NS entry and surrounding context
sed -n '100,125p' test/duckdb_test/result_each_test.rb

# Find where queries are executed
rg -n "def test_\|execute\|TEST_TABLES\[" test/duckdb_test/result_each_test.rb | head -20

# Look at how the test iterates through TEST_TABLES
sed -n '150,200p' test/duckdb_test/result_each_test.rb

Repository: suketa/ruby-duckdb

Length of output: 4189


🏁 Script executed:

# Check what happens with BIT which uses explicit casting
sed -n '115,120p' test/duckdb_test/result_each_test.rb

# Search for other types that use explicit casting in the test data
rg "::BIT|::TIME|CAST" test/duckdb_test/result_each_test.rb

Repository: suketa/ruby-duckdb

Length of output: 922


🏁 Script executed:

# Check if there are any recent commits that added TIME_NS
git log --oneline --all -- test/duckdb_test/result_each_test.rb | head -10

# Look for any TODOs or FIXMEs related to TIME_NS
rg "TODO|FIXME|TIME_NS" test/ lib/ ext/ -i

Repository: suketa/ruby-duckdb

Length of output: 2393


🏁 Script executed:

# Show the full commit that added TIME_NS support
git show 1903178 --stat

# Also check if there are any other TIME_NS tests or examples in the codebase
rg "TIME_NS" . --type=rb --type=c -B3 -A3 | grep -E "(test_|def |TIME_NS)" | head -30

Repository: suketa/ruby-duckdb

Length of output: 11518


🏁 Script executed:

# Check if there's any documentation or comments about TIME_NS syntax in DuckDB
rg "TIME_NS|time_ns" ext/duckdb/ -B5 -A5 | grep -E "(comment|TODO|//|/\*)" | head -20

# Check if there's a way to cast to TIME_NS
rg "CAST.*TIME" test/ lib/ -i | head -10

Repository: suketa/ruby-duckdb

Length of output: 835


🏁 Script executed:

# Look for the result_time_ns_test.rb file
find test -name "*time_ns*" -o -name "*TIME_NS*"

# If it exists, show its contents
cat test/duckdb_test/result_time_ns_test.rb 2>/dev/null || echo "File not found"

Repository: suketa/ruby-duckdb

Length of output: 76


🏁 Script executed:

# Search for TIME test patterns to see if there's a difference
rg -n "result_time.*test|TIME.*test" test/duckdb_test/ | grep -v result_each_test

# Look for the actual TIME entry in TEST_TABLES to compare with TIME_NS
sed -n '50,60p' test/duckdb_test/result_each_test.rb

Repository: suketa/ruby-duckdb

Length of output: 2045


🏁 Script executed:

# Check if result_time_ns_test.rb exists
ls -la test/duckdb_test/result_time_ns_test.rb 2>&1

# List all test files to see what exists
ls test/duckdb_test/*time* 2>/dev/null || echo "No time-related test files found"

# Check git status to see if the file was supposed to be there
git log --name-status --oneline 1903178 | head -30

Repository: suketa/ruby-duckdb

Length of output: 1036


🏁 Script executed:

# Check for recent commits after TIME_NS support was added
git log --oneline --all | head -20

# Check if there are any skipped tests related to TIME_NS
rg "skip.*TIME_NS|TIME_NS.*skip" test/

Repository: suketa/ruby-duckdb

Length of output: 1382


🏁 Script executed:

# Check if there are any comments or documentation about TIME_NS syntax
rg "TIME_NS|time_ns" . --type=md --type=txt 2>/dev/null | head -20

# Check if BIT uses explicit casting and if TIME_NS should too
rg "::BIT|::TIME|CAST.*TIME" test/duckdb_test/ -B2 -A2 | head -40

# Look at how result_time_tz_test.rb handles TIMETZ to see if TIME_NS needs similar setup
cat test/duckdb_test/result_time_tz_test.rb

Repository: suketa/ruby-duckdb

Length of output: 3743


🏁 Script executed:

# Check if TIME_NS needs special setup like ICU like TIMETZ does
rg "INSTALL|LOAD" test/duckdb_test/result_time_tz_test.rb

# Check if the prepare_test_table_and_data function has any special handling we missed
sed -n '117,135p' test/duckdb_test/result_each_test.rb

# Check if TIME_NS might need explicit casting like BIT does
rg -A30 "def prepare_test_table_and_data" test/duckdb_test/result_each_test.rb | head -40

Repository: suketa/ruby-duckdb

Length of output: 2276


🏁 Script executed:

# Compare how TIME and TIME_NS values are being parsed in the INSERT statement
rg "TIME.*'[0-9:]" test/duckdb_test/result_each_test.rb | grep -E "(TIME_NS|'TIME'|TIME[^_Z])"

# Check if we should be using CAST for TIME_NS
git show 1903178:test/duckdb_test/result_each_test.rb | grep -A2 -B2 "TIME_NS"

Repository: suketa/ruby-duckdb

Length of output: 2727


🏁 Script executed:

# Count how many TIME_NS entries exist in the current file
grep -n "TIME_NS" test/duckdb_test/result_each_test.rb | wc -l

# Show all TIME_NS entries with context
grep -n "TIME_NS" test/duckdb_test/result_each_test.rb -B1 -A1

# Check if this might be a syntax issue - does TIME_NS need explicit casting like BIT?
# Let me check if DuckDB documentation says TIME_NS needs casting
rg "::TIME|CAST.*TIME" test/ -i | head -10

Repository: suketa/ruby-duckdb

Length of output: 1609


🏁 Script executed:

# Check if there's any example of TIME_NS being used with CAST
git log --all -p | grep -A5 -B5 "TIME_NS.*CAST\|CAST.*TIME_NS" | head -20

# Search for any workarounds or issues related to TIME_NS literal parsing
rg "TIME_NS" . -i | grep -E "(TODO|FIXME|BUG|issue|workaround)"

Repository: suketa/ruby-duckdb

Length of output: 148


🏁 Script executed:

# Check if adding explicit casting would work by looking at similar patterns
# First, let's see what happens if we look at how the INSERT query would look

echo "Current TIME_NS test:"
echo "INSERT INTO tests VALUES ( '12:34:56.789123456' )"
echo ""
echo "Likely fix - with explicit casting:"
echo "INSERT INTO tests VALUES ( '12:34:56.789123456'::TIME_NS )"
echo ""

# Verify that BIT uses explicit casting and TIME doesn't
grep -E "BIT|'TIME'" test/duckdb_test/result_each_test.rb | grep -E "(::|\[')" | head -5

Repository: suketa/ruby-duckdb

Length of output: 950


Add explicit casting to TIME_NS literal in test data.

The TIME_NS test fails with NoMethodError: undefined method 'hour' for nil because the INSERT statement cannot parse the TIME_NS literal. Precision-dependent types like BIT require explicit casting (e.g., '010110'::BIT), and TIME_NS follows the same pattern.

Update line 110 in test/duckdb_test/result_each_test.rb to add explicit casting:

From:

[:ok, 'TIME_NS',      'TIME_NS',                      "'12:34:56.789123456'",                    Time,                 Time.parse('12:34:56.789123')                       ],

To:

[:ok, 'TIME_NS',      'TIME_NS',                      "'12:34:56.789123456'::TIME_NS",           Time,                 Time.parse('12:34:56.789123')                       ],

Without the casting, DuckDB returns NULL for the TIME_NS column, causing the nil error when the test tries to access time properties.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/duckdb_test/result_each_test.rb` at line 110, The TIME_NS test row is
inserting the literal "'12:34:56.789123456'" without an explicit type cast,
causing DuckDB to return NULL and later a NoMethodError when Time methods are
called; update the test data row where the column/type is 'TIME_NS' to cast the
literal to TIME_NS (change the literal to include ::TIME_NS) so the INSERT
parses the nanosecond-precision time correctly—look for the array entry
containing 'TIME_NS' in result_each_test.rb and modify the third value in that
tuple to "'12:34:56.789123456'::TIME_NS".

TIME_NS vector support was added in DuckDB v1.5.0. On 1.4.x the type
constant (39) is defined in the header but the runtime returns nil for
TIME_NS column values.

Add MINIMUM_DUCKDB_VERSION map and skip guard in the dynamically
generated test methods in result_each_test.rb.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/duckdb_test/result_each_test.rb`:
- Around line 166-169: The test currently calls
DuckDBTest.duckdb_library_version inside a dynamically defined method which can
resolve to the nested class DuckDBTest::DuckDBTest; change those calls to use
the top-level namespace by replacing DuckDBTest.duckdb_library_version with
::DuckDBTest.duckdb_library_version so the module method is always invoked
(apply this update in the dynamic test methods surrounding the
MINIMUM_DUCKDB_VERSION check and the other occurrence noted).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0ada2dc0-3ed0-4b99-b7e1-2aae56fe811b

📥 Commits

Reviewing files that changed from the base of the PR and between 1903178 and 9a0784f.

📒 Files selected for processing (1)
  • test/duckdb_test/result_each_test.rb

Inside `module DuckDBTest`, bare `DuckDBTest` resolves to
`DuckDBTest::DuckDBTest` (the test base class), not the top-level
module where `duckdb_library_version` is defined. Prefix with `::`
to force top-level lookup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@suketa suketa merged commit c2cc3ee into main Apr 4, 2026
41 checks passed
@suketa suketa deleted the support-time-ns branch April 4, 2026 10:36
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.

1 participant