Class: DuckDB::Appender

Inherits:
Object
  • Object
show all
Includes:
Converter
Defined in:
lib/duckdb/appender.rb,
ext/duckdb/appender.c

Overview

The DuckDB::Appender encapsulates DuckDB Appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE users (id INTEGER, name VARCHAR)')
appender = con.appender('users')
appender.append_row(1, 'Alice')

Constant Summary

Constants included from Converter

Converter::EPOCH, Converter::EPOCH_UTC, Converter::FLIP_HUGEINT, Converter::HALF_HUGEINT, Converter::HALF_HUGEINT_BIT

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Converter

_parse_date, _parse_deciaml, _parse_time, _to_date, _to_decimal_from_hugeint, _to_decimal_from_value, _to_hugeint_from_vector, _to_infinity, _to_interval_from_vector, _to_query_progress, _to_time, _to_time_from_duckdb_time, _to_time_from_duckdb_time_tz, _to_time_from_duckdb_timestamp_ms, _to_time_from_duckdb_timestamp_ns, _to_time_from_duckdb_timestamp_s, _to_time_from_duckdb_timestamp_tz, _to_uuid_from_vector

Constructor Details

#initialize(con, schema, table) ⇒ Object



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'ext/duckdb/appender.c', line 127

static VALUE appender_initialize(VALUE self, VALUE con, VALUE schema, VALUE table) {

    rubyDuckDBConnection *ctxcon;
    rubyDuckDBAppender *ctx;
    char *pschema = 0;

    if (!rb_obj_is_kind_of(con, cDuckDBConnection)) {
        rb_raise(rb_eTypeError, "1st argument should be instance of DackDB::Connection");
    }

    TypedData_Get_Struct(self, rubyDuckDBAppender, &appender_data_type, ctx);
    ctxcon = get_struct_connection(con);

    if (schema != Qnil) {
        pschema = StringValuePtr(schema);
    }

    if (duckdb_appender_create(ctxcon->con, pschema, StringValuePtr(table), &(ctx->appender)) == DuckDBError) {
        rb_raise(eDuckDBError, "failed to create appender");
    }
    return self;
}

Class Method Details

.DuckDB::Appender.create_queryDuckDB::Appender

Returns a new Appender instance created from a query.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE users (id INTEGER, name VARCHAR)')
query = 'INSERT OR REPLACE INTO users SELECT i, val FROM my_appended_data'
types = [DuckDB::LogicalType::INTEGER, DuckDB::LogicalType::VARCHAR]
appender = DuckDB::Appender.create_query(con, query, types, 'my_appended_data', %w[i val])

Returns:



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'ext/duckdb/appender.c', line 78

static VALUE appender_s_create_query(VALUE klass, VALUE con, VALUE query, VALUE types, VALUE table, VALUE columns) {
    rubyDuckDBConnection *ctxcon;
    rubyDuckDBAppender *ctx;
    char *query_str = StringValuePtr(query);
    char *table_name = NULL;
    const char **column_names = NULL;
    idx_t column_count = 0;
    duckdb_logical_type *type_array = NULL;
    VALUE appender = Qnil;

    if (!rb_obj_is_kind_of(con, cDuckDBConnection)) {
        rb_raise(rb_eTypeError, "1st argument should be instance of DackDB::Connection");
    }
    if (rb_obj_is_kind_of(types, rb_cArray) == Qfalse) {
        rb_raise(rb_eTypeError, "2nd argument should be an Array");
    }
    column_count = RARRAY_LEN(types);
    type_array = ALLOCA_N(duckdb_logical_type, (size_t)column_count);
    for (idx_t i = 0; i < column_count; i++) {
        VALUE type_val = rb_ary_entry(types, i);
        rubyDuckDBLogicalType *type_ctx = get_struct_logical_type(type_val);
        type_array[i] = type_ctx->logical_type;
    }

    if (table != Qnil) {
        table_name = StringValuePtr(table);
    }
    if (columns != Qnil) {
        if (rb_obj_is_kind_of(columns, rb_cArray) == Qfalse) {
            rb_raise(rb_eTypeError, "4th argument should be an Array or nil");
        }
        idx_t col_count = RARRAY_LEN(columns);
        column_names = ALLOCA_N(const char *, (size_t)col_count);
        for (idx_t i = 0; i < col_count; i++) {
            VALUE col_name_val = rb_ary_entry(columns, i);
            column_names[i] = StringValuePtr(col_name_val);
        }
    }
    ctxcon = get_struct_connection(con);
    appender = allocate(klass);
    TypedData_Get_Struct(appender, rubyDuckDBAppender, &appender_data_type, ctx);
    if (duckdb_appender_create_query(ctxcon->con, query_str, column_count, type_array, table_name, column_names, &ctx->appender) == DuckDBError) {
        rb_raise(eDuckDBError, "failed to create appender from query");
    }

    return appender;
}

Instance Method Details

#append(value) ⇒ Object

appends value.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE users (id INTEGER, name VARCHAR)')
appender = con.appender('users')
appender.append(1)
appender.append('Alice')
appender.end_row


583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
# File 'lib/duckdb/appender.rb', line 583

def append(value)
  case value
  when NilClass
    append_null
  when Float
    append_double(value)
  when Integer
    case value
    when RANGE_INT16
      append_int16(value)
    when RANGE_INT32
      append_int32(value)
    when RANGE_INT64
      append_int64(value)
    else
      append_hugeint(value)
    end
  when String
    blob?(value) ? append_blob(value) : append_varchar(value)
  when TrueClass, FalseClass
    append_bool(value)
  when Time
    append_timestamp(value)
  when Date
    append_date(value)
  when DuckDB::Interval
    append_interval(value)
  else
    raise(DuckDB::Error, "not supported type #{value} (#{value.class})")
  end
end

#append_blob(value) ⇒ Object

call-seq:

appender.append_blob(val) -> self

Appends a varchar value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE values (value BLOB)')
appender = con.appender('values')
appender
  .append('\0\1\2\3\4\5'.encode(Encoding::BINARY))
  .end_row
  .flush


387
388
389
390
391
# File 'lib/duckdb/appender.rb', line 387

def append_blob(value)
  return self if _append_blob(value)

  raise_appender_error('failed to append_blob')
end

#append_bool(value) ⇒ Object

call-seq:

appender.append_bool(val) -> self

Appends a boolean value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE users (id INTEGER, active BOOLEAN)')
appender = con.appender('users')
appender
  .append_int32(1)
  .append_bool(true)
  .end_row
  .flush


118
119
120
121
122
# File 'lib/duckdb/appender.rb', line 118

def append_bool(value)
  return self if _append_bool(value)

  raise_appender_error('failed to append_bool')
end

#append_date(value) ⇒ Object

call-seq:

appender.append_date(val) -> self

Appends a date value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE dates (date_value DATE)')
appender = con.appender('dates')
appender.append_date(Date.today)
# or
# appender.append_date(Time.now)
# appender.append_date('2021-10-10')
appender.end_row
appender.flush


495
496
497
498
499
500
501
# File 'lib/duckdb/appender.rb', line 495

def append_date(value)
  date = _parse_date(value)

  return self if _append_date(date.year, date.month, date.day)

  raise_appender_error('failed to append_date')
end

#append_defaultObject

call-seq:

appender.append_default -> self

Appends a default value to the current row in the appender. If the column does not have a default value, this method appends a NULL value.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE values (value INTEGER DEFAULT 1)')
appender = con.appender('values')
appender
  .append_default
  .end_row
  .flush


429
430
431
432
433
# File 'lib/duckdb/appender.rb', line 429

def append_default
  return self if _append_default

  raise_appender_error('failed to append_default')
end

#append_double(value) ⇒ Object

call-seq:

appender.append_double(val) -> self

Appends a double value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE numbers (num DOUBLE)')
appender = con.appender('numbers')
appender
  .append_double(1.23)
  .end_row
  .flush


327
328
329
330
331
# File 'lib/duckdb/appender.rb', line 327

def append_double(value)
  return self if _append_double(value)

  raise_appender_error('failed to append_double')
end

#append_float(value) ⇒ Object

call-seq:

appender.append_float(val) -> self

Appends a float value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE numbers (num FLOAT)')
appender = con.appender('numbers')
appender
  .append_float(1.23)
  .end_row
  .flush


307
308
309
310
311
# File 'lib/duckdb/appender.rb', line 307

def append_float(value)
  return self if _append_float(value)

  raise_appender_error('failed to append_float')
end

#append_hugeint(value) ⇒ Object

call-seq:

appender.append_hugeint(val) -> self

Appends a huge int value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE numbers (num HUGEINT)')
appender = con.appender('numbers')
appender
  .append_hugeint(-170_141_183_460_469_231_731_687_303_715_884_105_727)
  .end_row
  .flush


449
450
451
452
453
454
455
# File 'lib/duckdb/appender.rb', line 449

def append_hugeint(value)
  lower, upper = integer_to_hugeint(value)

  return self if _append_hugeint(lower, upper)

  raise_appender_error('failed to append_hugeint')
end

#append_int16(value) ⇒ Object

call-seq:

appender.append_int16(val) -> self

Appends an int16(SMALLINT) value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE users (id INTEGER, age SMALLINT)')
appender = con.appender('users')
appender
  .append_int32(1)
  .append_int16(20)
  .end_row
  .flush


161
162
163
164
165
# File 'lib/duckdb/appender.rb', line 161

def append_int16(value)
  return self if _append_int16(value)

  raise_appender_error('failed to append_int16')
end

#append_int32(value) ⇒ Object

call-seq:

appender.append_int32(val) -> self

Appends an int32(INTEGER) value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE users (id INTEGER, age INTEGER)')
appender = con.appender('users')
appender
  .append_int32(1)
  .append_int32(20)
  .end_row
  .flush


182
183
184
185
186
# File 'lib/duckdb/appender.rb', line 182

def append_int32(value)
  return self if _append_int32(value)

  raise_appender_error('failed to append_int32')
end

#append_int64(value) ⇒ Object

call-seq:

appender.append_int64(val) -> self

Appends an int64(BIGINT) value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE users (id INTEGER, age BIGINT)')
appender = con.appender('users')
appender
  .append_int32(1)
  .append_int64(20)
  .end_row
  .flush


203
204
205
206
207
# File 'lib/duckdb/appender.rb', line 203

def append_int64(value)
  return self if _append_int64(value)

  raise_appender_error('failed to append_int64')
end

#append_int8(value) ⇒ Object

call-seq:

appender.append_int8(val) -> self

Appends an int8(TINYINT) value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE users (id INTEGER, age TINYINT)')
appender = con.appender('users')
appender
  .append_int32(1)
  .append_int8(20)
  .end_row
  .flush


140
141
142
143
144
# File 'lib/duckdb/appender.rb', line 140

def append_int8(value)
  return self if _append_int8(value)

  raise_appender_error('failed to append_int8')
end

#append_interval(value) ⇒ Object

call-seq:

appender.append_interval(val) -> self

Appends an interval value to the current row in the appender. The argument must be ISO8601 duration format.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE intervals (interval_value INTERVAL)')
appender = con.appender('intervals')
appender
  .append_interval('P1Y2D') # => append 1 year 2 days interval.
  .end_row
  .flush


565
566
567
568
569
570
571
# File 'lib/duckdb/appender.rb', line 565

def append_interval(value)
  value = Interval.to_interval(value)

  return self if _append_interval(value.interval_months, value.interval_days, value.interval_micros)

  raise_appender_error('failed to append_interval')
end

#append_nullObject

call-seq:

appender.append_null -> self

Appends a NULL value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE values (value INTEGER)')
appender = con.appender('values')
appender
  .append_null
  .end_row
  .flush


407
408
409
410
411
# File 'lib/duckdb/appender.rb', line 407

def append_null
  return self if _append_null

  raise_appender_error('failed to append_null')
end

#append_row(*args) ⇒ Object

append a row.

appender.append_row(1, 'Alice')

is same as:

appender.append(2)
appender.append('Alice')
appender.end_row


624
625
626
627
628
629
# File 'lib/duckdb/appender.rb', line 624

def append_row(*args)
  args.each do |arg|
    append(arg)
  end
  end_row
end

#append_time(value) ⇒ Object

call-seq:

appender.append_time(val) -> self

Appends a time value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE times (time_value TIME)')
appender = con.appender('times')
appender.append_time(Time.now)
# or
# appender.append_time('01:01:01')
appender.end_row
appender.flush


518
519
520
521
522
523
524
# File 'lib/duckdb/appender.rb', line 518

def append_time(value)
  time = _parse_time(value)

  return self if _append_time(time.hour, time.min, time.sec, time.usec)

  raise_appender_error('failed to append_time')
end

#append_timestamp(value) ⇒ Object

call-seq:

appender.append_timestamp(val) -> self

Appends a timestamp value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE timestamps (timestamp_value TIMESTAMP)')
appender = con.appender('timestamps')
appender.append_time(Time.now)
# or
# appender.append_time(Date.today)
# appender.append_time('2021-08-01 01:01:01')
appender.end_row
appender.flush


542
543
544
545
546
547
548
# File 'lib/duckdb/appender.rb', line 542

def append_timestamp(value)
  time = to_time(value)

  return self if _append_timestamp(time.year, time.month, time.day, time.hour, time.min, time.sec, time.nsec / 1000)

  raise_appender_error('failed to append_timestamp')
end

#append_uhugeint(value) ⇒ Object

call-seq:

appender.append_uhugeint(val) -> self

Appends an unsigned huge int value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE numbers (num UHUGEINT)')
appender = con.appender('numbers')
appender
  .append_hugeint(340_282_366_920_938_463_463_374_607_431_768_211_455)
  .end_row
  .flush


471
472
473
474
475
476
477
# File 'lib/duckdb/appender.rb', line 471

def append_uhugeint(value)
  lower, upper = integer_to_hugeint(value)

  return self if _append_uhugeint(lower, upper)

  raise_appender_error('failed to append_uhugeint')
end

#append_uint16(value) ⇒ Object

call-seq:

appender.append_uint16(val) -> self

Appends an uint16 value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE users (id INTEGER, age USMALLINT)')
appender = con.appender('users')
appender
  .append_int32(1)
  .append_uint16(20)
  .end_row
  .flush


245
246
247
248
249
# File 'lib/duckdb/appender.rb', line 245

def append_uint16(value)
  return self if _append_uint16(value)

  raise_appender_error('failed to append_uint16')
end

#append_uint32(value) ⇒ Object

call-seq:

appender.append_uint32(val) -> self

Appends an uint32 value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE users (id INTEGER, age UINTEGER)')
appender = con.appender('users')
appender
  .append_int32(1)
  .append_uint32(20)
  .end_row
  .flush


266
267
268
269
270
# File 'lib/duckdb/appender.rb', line 266

def append_uint32(value)
  return self if _append_uint32(value)

  raise_appender_error('failed to append_uint32')
end

#append_uint64(value) ⇒ Object

call-seq:

appender.append_uint64(val) -> self

Appends an uint64 value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE users (id INTEGER, age UBIGINT)')
appender = con.appender('users')
Appender
  .append_int32(1)
  .append_uint64(20)
  .end_row
  .flush


287
288
289
290
291
# File 'lib/duckdb/appender.rb', line 287

def append_uint64(value)
  return self if _append_uint64(value)

  raise_appender_error('failed to append_uint64')
end

#append_uint8(value) ⇒ Object

call-seq:

appender.append_uint8(val) -> self

Appends an uint8 value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE users (id INTEGER, age UTINYINT)')
appender = con.appender('users')
appender
  .append_int32(1)
  .append_uint8(20)
  .end_row
  .flush


224
225
226
227
228
# File 'lib/duckdb/appender.rb', line 224

def append_uint8(value)
  return self if _append_uint8(value)

  raise_appender_error('failed to append_uint8')
end

#append_varchar(value) ⇒ Object

call-seq:

appender.append_varchar(val) -> self

Appends a varchar value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE names (name VARCHAR)')
appender = con.appender('names')
appender
  .append_varchar('Alice')
  .end_row
  .flush


347
348
349
350
351
# File 'lib/duckdb/appender.rb', line 347

def append_varchar(value)
  return self if _append_varchar(value)

  raise_appender_error('failed to append_varchar')
end

#append_varchar_length(value, length) ⇒ Object

call-seq:

appender.append_varchar_length(val, len) -> self

Appends a varchar value to the current row in the appender.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE names (name VARCHAR)')
appender = con.appender('names')
appender
  .append_varchar_length('Alice', 5)
  .end_row
  .flush


367
368
369
370
371
# File 'lib/duckdb/appender.rb', line 367

def append_varchar_length(value, length)
  return self if _append_varchar_length(value, length)

  raise_appender_error('failed to append_varchar_length')
end

#begin_rowObject

:call-seq:

appender.begin_row -> self

A nop method, provided for backwards compatibility reasons. Does nothing. Only ‘end_row` is required.



34
35
36
# File 'lib/duckdb/appender.rb', line 34

def begin_row
  self
end

#closeObject

:call-seq:

appender.close -> self

Closes the appender by flushing all intermediate states and closing it for further appends. If flushing the data triggers a constraint violation or any other error, then all data is invalidated, and this method raises DuckDB::Error.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE users (id INTEGER, name VARCHAR)')
appender = con.appender('users')
appender
  .append_int32(1)
  .append_varchar('Alice')
  .end_row
  .close


97
98
99
100
101
# File 'lib/duckdb/appender.rb', line 97

def close
  return self if _close

  raise_appender_error('failed to close')
end

#end_rowObject

call-seq:

appender.end_row -> self

Finish the current row of appends. After end_row is called, the next row can be appended.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE users (id INTEGER, name VARCHAR)')
appender = con.appender('users')
appender
  .append_int32(1)
  .append_varchar('Alice')
  .end_row


51
52
53
54
55
# File 'lib/duckdb/appender.rb', line 51

def end_row
  return self if _end_row

  raise_appender_error('failed to end_row')
end

#error_messageString

Returns the error message of the appender. If there is no error, then it returns nil.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE users (id INTEGER, name VARCHAR)')
appender = con.appender('users')
appender.error_message # => nil

Returns:

  • (String)


162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'ext/duckdb/appender.c', line 162

static VALUE appender_error_message(VALUE self) {
    rubyDuckDBAppender *ctx;
#ifdef HAVE_DUCKDB_H_GE_V1_4_0
    duckdb_error_data error_data;
#endif
    const char *msg = NULL;
    VALUE rb_msg = Qnil;
    TypedData_Get_Struct(self, rubyDuckDBAppender, &appender_data_type, ctx);

#ifdef HAVE_DUCKDB_H_GE_V1_4_0
    error_data = duckdb_appender_error_data(ctx->appender);
    if (duckdb_error_data_has_error(error_data)) {
        msg = duckdb_error_data_message(error_data);
        rb_msg = rb_str_new2(msg);
    }
    duckdb_destroy_error_data(&error_data);
#else
    msg = duckdb_appender_error(ctx->appender);
    if (msg != NULL) {
        rb_msg = rb_str_new2(msg);
    }
#endif
    return rb_msg;
}

#flushObject

:call-seq:

appender.flush -> self

Flushes the appender to the table, forcing the cache of the appender to be cleared. If flushing the data triggers a constraint violation or any other error, then all data is invalidated, and this method raises DuckDB::Error.

require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE users (id INTEGER, name VARCHAR)')
appender = con.appender('users')
appender
  .append_int32(1)
  .append_varchar('Alice')
  .end_row
  .flush


74
75
76
77
78
# File 'lib/duckdb/appender.rb', line 74

def flush
  return self if _flush

  raise_appender_error('failed to flush')
end