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
tenderlove committed Jan 25, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 44ab2aa commit dd70e03
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
@@ -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")

19 changes: 17 additions & 2 deletions ext/sqlite3/statement.c
Original file line number Diff line number Diff line change
@@ -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.
@@ -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;
}
16 changes: 16 additions & 0 deletions test/test_statement.rb
Original file line number Diff line number Diff line change
@@ -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(?)")

0 comments on commit dd70e03

Please sign in to comment.