Skip to content

Commit 7bcf4d6

Browse files
authored
Merge pull request #839 from suketa/duckdb_append_error
add DuckDB::Appender#error_message
2 parents f0a44f9 + 820a796 commit 7bcf4d6

File tree

4 files changed

+83
-23
lines changed

4 files changed

+83
-23
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
All notable changes to this project will be documented in this file.
44
# Unreleased
5+
- add 'DuckDB::Appender#error_message'.
6+
- fix error message when DuckDB::Appender#flush failed.
7+
58
# 1.1.3.1 - 2024-11-27
69
- fix to `DuckDB::Connection#query` with multiple SQL statements. Calling PreparedStatement#destroy after each statement executed.
710
- install valgrind in docker development environment.

ext/duckdb/appender.c

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ static void deallocate(void *);
66
static VALUE allocate(VALUE klass);
77
static size_t memsize(const void *p);
88
static VALUE appender_initialize(VALUE klass, VALUE con, VALUE schema, VALUE table);
9+
static VALUE appender_error_message(VALUE self);
910
static VALUE appender_begin_row(VALUE self);
1011
static VALUE appender_end_row(VALUE self);
1112
static VALUE appender_append_bool(VALUE self, VALUE val);
@@ -34,7 +35,7 @@ static VALUE appender__append_time(VALUE self, VALUE hour, VALUE min, VALUE sec,
3435
static VALUE appender__append_timestamp(VALUE self, VALUE year, VALUE month, VALUE day, VALUE hour, VALUE min, VALUE sec, VALUE micros);
3536
static VALUE appender__append_hugeint(VALUE self, VALUE lower, VALUE upper);
3637
static VALUE appender__append_uhugeint(VALUE self, VALUE lower, VALUE upper);
37-
static VALUE appender_flush(VALUE self);
38+
static VALUE appender__flush(VALUE self);
3839
static VALUE appender_close(VALUE self);
3940

4041
static const rb_data_type_t appender_data_type = {
@@ -82,6 +83,30 @@ static VALUE appender_initialize(VALUE self, VALUE con, VALUE schema, VALUE tabl
8283
return self;
8384
}
8485

86+
/* call-seq:
87+
* appender.error_message -> String
88+
*
89+
* Returns the error message of the appender. If there is no error, then it returns nil.
90+
*
91+
* require 'duckdb'
92+
* db = DuckDB::Database.open
93+
* con = db.connect
94+
* con.query('CREATE TABLE users (id INTEGER, name VARCHAR)')
95+
* appender = con.appender('users')
96+
* appender.error_message # => nil
97+
*/
98+
static VALUE appender_error_message(VALUE self) {
99+
rubyDuckDBAppender *ctx;
100+
const char *msg;
101+
TypedData_Get_Struct(self, rubyDuckDBAppender, &appender_data_type, ctx);
102+
103+
msg = duckdb_appender_error(ctx->appender);
104+
if (msg == NULL) {
105+
return Qnil;
106+
}
107+
return rb_str_new2(msg);
108+
}
109+
85110
/* call-seq:
86111
* appender.begin_row -> self
87112
*
@@ -507,32 +532,15 @@ static VALUE appender__append_uhugeint(VALUE self, VALUE lower, VALUE upper) {
507532
return self;
508533
}
509534

510-
/* call-seq:
511-
* appender.flush -> self
512-
*
513-
* Flushes the appender to the table, forcing the cache of the appender to be cleared. If flushing the data triggers a
514-
* constraint violation or any other error, then all data is invalidated, and this method raises DuckDB::Error.
515-
*
516-
* require 'duckdb'
517-
* db = DuckDB::Database.open
518-
* con = db.connect
519-
* con.query('CREATE TABLE users (id INTEGER, name VARCHAR)')
520-
* appender = con.appender('users')
521-
* appender
522-
* .begin_row
523-
* .append_int32(1)
524-
* .append_varchar('Alice')
525-
* .end_row
526-
* .flush
527-
*/
528-
static VALUE appender_flush(VALUE self) {
535+
/* :nodoc: */
536+
static VALUE appender__flush(VALUE self) {
529537
rubyDuckDBAppender *ctx;
530538
TypedData_Get_Struct(self, rubyDuckDBAppender, &appender_data_type, ctx);
531539

532540
if (duckdb_appender_flush(ctx->appender) == DuckDBError) {
533-
rb_raise(eDuckDBError, "failed to flush");
541+
return Qfalse;
534542
}
535-
return self;
543+
return Qtrue;
536544
}
537545

538546
static VALUE appender_close(VALUE self) {
@@ -552,6 +560,7 @@ void rbduckdb_init_duckdb_appender(void) {
552560
cDuckDBAppender = rb_define_class_under(mDuckDB, "Appender", rb_cObject);
553561
rb_define_alloc_func(cDuckDBAppender, allocate);
554562
rb_define_method(cDuckDBAppender, "initialize", appender_initialize, 3);
563+
rb_define_method(cDuckDBAppender, "error_message", appender_error_message, 0);
555564
rb_define_method(cDuckDBAppender, "begin_row", appender_begin_row, 0);
556565
rb_define_method(cDuckDBAppender, "end_row", appender_end_row, 0);
557566
rb_define_method(cDuckDBAppender, "append_bool", appender_append_bool, 1);
@@ -569,13 +578,13 @@ void rbduckdb_init_duckdb_appender(void) {
569578
rb_define_method(cDuckDBAppender, "append_varchar_length", appender_append_varchar_length, 2);
570579
rb_define_method(cDuckDBAppender, "append_blob", appender_append_blob, 1);
571580
rb_define_method(cDuckDBAppender, "append_null", appender_append_null, 0);
572-
rb_define_method(cDuckDBAppender, "flush", appender_flush, 0);
573581
rb_define_method(cDuckDBAppender, "close", appender_close, 0);
574582

575583
#ifdef HAVE_DUCKDB_H_GE_V1_1_0
576584
rb_define_method(cDuckDBAppender, "append_default", appender_append_default, 0);
577585
#endif
578586

587+
rb_define_private_method(cDuckDBAppender, "_flush", appender__flush, 0);
579588
rb_define_private_method(cDuckDBAppender, "_append_date", appender__append_date, 3);
580589
rb_define_private_method(cDuckDBAppender, "_append_interval", appender__append_interval, 3);
581590
rb_define_private_method(cDuckDBAppender, "_append_time", appender__append_time, 4);

lib/duckdb/appender.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,30 @@ class Appender
2323
private_constant :RANGE_INT16, :RANGE_INT32, :RANGE_INT64
2424
# :startdoc:
2525

26+
# :call-seq:
27+
# flush -> self
28+
#
29+
# Flushes the appender to the table, forcing the cache of the appender to be cleared.
30+
# If flushing the data triggers a constraint violation or any other error, then all
31+
# data is invalidated, and this method raises DuckDB::Error.
32+
#
33+
# require 'duckdb'
34+
# db = DuckDB::Database.open
35+
# con = db.connect
36+
# con.query('CREATE TABLE users (id INTEGER, name VARCHAR)')
37+
# appender = con.appender('users')
38+
# appender
39+
# .begin_row
40+
# .append_int32(1)
41+
# .append_varchar('Alice')
42+
# .end_row
43+
# .flush
44+
def flush
45+
return self if _flush
46+
47+
raise_appender_error('failed to flush')
48+
end
49+
2650
# appends huge int value.
2751
#
2852
# require 'duckdb'
@@ -196,6 +220,11 @@ def append_row(*args)
196220

197221
private
198222

223+
def raise_appender_error(default_message) # :nodoc:
224+
message = error_message
225+
raise DuckDB::Error, message || default_message
226+
end
227+
199228
def blob?(value) # :nodoc:
200229
value.instance_of?(DuckDB::Blob) || value.encoding == Encoding::BINARY
201230
end

test/duckdb_test/appender_test.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,25 @@ def assert_duckdb_appender(expected, type, &block)
8888
teardown
8989
end
9090

91+
def test_flush
92+
appender = create_appender('col BOOLEAN')
93+
appender
94+
.begin_row
95+
.append_bool(true)
96+
.end_row
97+
assert_equal(appender.__id__, appender.flush.__id__)
98+
end
99+
100+
def test_flush_with_exception
101+
appender = create_appender('col BOOLEAN NOT NULL')
102+
appender
103+
.begin_row
104+
.append_null
105+
.end_row
106+
exception = assert_raises(DuckDB::Error) { appender.flush }
107+
assert_match(/NOT NULL constraint failed/, exception.message)
108+
end
109+
91110
def test_append_bool
92111
assert_duckdb_appender(true, 'BOOLEAN') { |a| a.append_bool(true) }
93112
assert_duckdb_appender(false, 'BOOLEAN') { |a| a.append_bool(false) }

0 commit comments

Comments
 (0)