diff --git a/ext/sqlite3/extconf.rb b/ext/sqlite3/extconf.rb index 733e22d5..c648d9e9 100644 --- a/ext/sqlite3/extconf.rb +++ b/ext/sqlite3/extconf.rb @@ -109,6 +109,10 @@ def configure_extension abort_could_not_find(libname) unless find_library(libname, "sqlite3_libversion_number", "sqlite3.h") + # Truffle Ruby doesn't support this yet: + # https://github.com/oracle/truffleruby/issues/3408 + have_func("rb_enc_interned_str_cstr") + # Functions defined in 1.9 but not 1.8 have_func("rb_proc_arity") diff --git a/ext/sqlite3/statement.c b/ext/sqlite3/statement.c index ad1765f2..a3fc7abd 100644 --- a/ext/sqlite3/statement.c +++ b/ext/sqlite3/statement.c @@ -364,6 +364,21 @@ column_count(VALUE self) return INT2NUM(sqlite3_column_count(ctx->st)); } +#if HAVE_RB_ENC_INTERNED_STR_CSTR +static VALUE +interned_utf8_cstr(const char *str) +{ + return rb_enc_interned_str_cstr(str, rb_utf8_encoding()); +} +#else +static VALUE +interned_utf8_cstr(const char *str) +{ + VALUE rb_str = rb_utf8_str_new_cstr(str); + return rb_funcall(rb_str, rb_intern("-@"), 0); +} +#endif + /* call-seq: stmt.column_name(index) * * Get the column name at +index+. 0 based. @@ -382,8 +397,7 @@ column_name(VALUE self, VALUE index) VALUE ret = Qnil; if (name) { - ret = SQLITE3_UTF8_STR_NEW2(name); - rb_obj_freeze(ret); + ret = interned_utf8_cstr(name); } return ret; } diff --git a/test/test_statement.rb b/test/test_statement.rb index c4a22662..c74f9a22 100644 --- a/test/test_statement.rb +++ b/test/test_statement.rb @@ -48,6 +48,23 @@ def test_raises_type_error end end + def test_column_names_are_deduped + @db.execute "CREATE TABLE 'things' ('float' float, 'int' int, 'text' blob, 'string' string, 'nil' string)" + stmt = @db.prepare "SELECT float, int, text, string, nil FROM things" + assert_equal ["float", "int", "text", "string", "nil"], stmt.columns + columns = stmt.columns + stmt.close + + stmt = @db.prepare "SELECT float, int, text, string, nil FROM things" + # Make sure this new statement returns the same interned strings + stmt.columns.each_with_index do |str, i| + assert_predicate columns[i], :frozen? + assert_same columns[i], str + end + ensure + stmt&.close + end + def test_insert_duplicate_records @db.execute 'CREATE TABLE "things" ("name" varchar(20) CONSTRAINT "index_things_on_name" UNIQUE)' stmt = @db.prepare("INSERT INTO things(name) VALUES(?)")