Skip to content

Commit

Permalink
Intern column names so we always get the same string
Browse files Browse the repository at this point in the history
When we get column names back from the database, it's very common to
always return the same strings. This patch uses Ruby's "interned string"
API so that we're always getting the same string objects back from the database.

Fixes: #155
  • Loading branch information
tenderlove committed Jan 25, 2024
1 parent 44ab2aa commit dd70e03
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 2 deletions.
4 changes: 4 additions & 0 deletions ext/sqlite3/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
19 changes: 17 additions & 2 deletions ext/sqlite3/statement.c
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,22 @@ 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);
rb_obj_freeze(rb_str);
return rb_funcall(rb_str, rb_intern("-@"), 0);
}
#endif

/* call-seq: stmt.column_name(index)
*
* Get the column name at +index+. 0 based.
Expand All @@ -382,8 +398,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;
}
Expand Down
16 changes: 16 additions & 0 deletions test/test_statement.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,22 @@ 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_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(?)")
Expand Down

0 comments on commit dd70e03

Please sign in to comment.