提交 8cc51500 编写于 作者: R Rafael Mendonça França

Merge pull request #20041 from akshay-vishnoi/sqlite_collation

SQLite3: Add collation support for string and text columns
* SQLite: `:collation` support for string and text columns.
Example:
create_table :foo do |t|
t.string :string_nocase, collation: 'NOCASE'
t.text :text_rtrim, collation: 'RTRIM'
end
add_column :foo, :title, :string, collation: 'RTRIM'
change_column :foo, :title, :string, collation: 'NOCASE'
*Akshay Vishnoi*
* Allow the use of symbols or strings to specify enum values in test
fixtures:
......
module ActiveRecord
module ConnectionAdapters
module SQLite3
class SchemaCreation < AbstractAdapter::SchemaCreation
private
def add_column_options!(sql, options)
if options[:collation]
sql << " COLLATE \"#{options[:collation]}\""
end
super
end
end
end
end
end
require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/connection_adapters/statement_pool'
require 'active_record/connection_adapters/sqlite3/schema_creation'
gem 'sqlite3', '~> 1.3.6'
require 'sqlite3'
......@@ -84,6 +85,10 @@ def dealloc(stmt)
end
end
def schema_creation # :nodoc:
SQLite3::SchemaCreation.new self
end
def initialize(connection, logger, connection_options, config)
super(connection, logger)
......@@ -344,9 +349,10 @@ def columns(table_name) #:nodoc:
field["dflt_value"] = $1.gsub('""', '"')
end
collation = field['collation']
sql_type = field['type']
type_metadata = fetch_type_metadata(sql_type)
new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0)
new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0, nil, collation)
end
end
......@@ -441,6 +447,7 @@ def change_column(table_name, column_name, type, options = {}) #:nodoc:
self.null = options[:null] if options.include?(:null)
self.precision = options[:precision] if options.include?(:precision)
self.scale = options[:scale] if options.include?(:scale)
self.collation = options[:collation] if options.include?(:collation)
end
end
end
......@@ -454,9 +461,9 @@ def rename_column(table_name, column_name, new_column_name) #:nodoc:
protected
def table_structure(table_name)
structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA')
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
structure
table_structure_with_collation(table_name, structure)
end
def alter_table(table_name, options = {}) #:nodoc:
......@@ -491,7 +498,7 @@ def copy_table(from, to, options = {}) #:nodoc:
@definition.column(column_name, column.type,
:limit => column.limit, :default => column.default,
:precision => column.precision, :scale => column.scale,
:null => column.null)
:null => column.null, collation: column.collation)
end
yield @definition if block_given?
end
......@@ -553,6 +560,46 @@ def translate_exception(exception, message)
super
end
end
private
COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
def table_structure_with_collation(table_name, basic_structure)
collation_hash = {}
sql = "SELECT sql FROM
(SELECT * FROM sqlite_master UNION ALL
SELECT * FROM sqlite_temp_master)
WHERE type='table' and name='#{ table_name }' \;"
# Result will have following sample string
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
# "password_digest" varchar COLLATE "NOCASE");
result = exec_query(sql, 'SCHEMA').first
if result
# Splitting with left parantheses and picking up last will return all
# columns separated with comma(,).
columns_string = result["sql"].split('(').last
columns_string.split(',').each do |column_string|
# This regex will match the column name and collation type and will save
# the value in $1 and $2 respectively.
collation_hash[$1] = $2 if (COLLATE_REGEX =~ column_string)
end
basic_structure.map! do |column|
column_name = column['name']
if collation_hash.has_key? column_name
column['collation'] = collation_hash[column_name]
end
column
end
else
basic_structure.to_hash
end
end
end
end
end
require "cases/helper"
require 'support/schema_dumping_helper'
class SQLite3CollationTest < ActiveRecord::TestCase
include SchemaDumpingHelper
def setup
@connection = ActiveRecord::Base.connection
@connection.create_table :collation_table_sqlite3, force: true do |t|
t.string :string_nocase, collation: 'NOCASE'
t.text :text_rtrim, collation: 'RTRIM'
end
end
def teardown
@connection.drop_table :collation_table_sqlite3, if_exists: true
end
test "string column with collation" do
column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == 'string_nocase' }
assert_equal :string, column.type
assert_equal 'NOCASE', column.collation
end
test "text column with collation" do
column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == 'text_rtrim' }
assert_equal :text, column.type
assert_equal 'RTRIM', column.collation
end
test "add column with collation" do
@connection.add_column :collation_table_sqlite3, :title, :string, collation: 'RTRIM'
column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == 'title' }
assert_equal :string, column.type
assert_equal 'RTRIM', column.collation
end
test "change column with collation" do
@connection.add_column :collation_table_sqlite3, :description, :string
@connection.change_column :collation_table_sqlite3, :description, :text, collation: 'RTRIM'
column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == 'description' }
assert_equal :text, column.type
assert_equal 'RTRIM', column.collation
end
test "schema dump includes collation" do
output = dump_table_schema("collation_table_sqlite3")
assert_match %r{t.string\s+"string_nocase",\s+collation: "NOCASE"$}, output
assert_match %r{t.text\s+"text_rtrim",\s+collation: "RTRIM"$}, output
end
end
......@@ -421,14 +421,14 @@ def test_respond_to_disable_extension
end
def test_statement_closed
db = SQLite3::Database.new(ActiveRecord::Base.
db = ::SQLite3::Database.new(ActiveRecord::Base.
configurations['arunit']['database'])
statement = SQLite3::Statement.new(db,
statement = ::SQLite3::Statement.new(db,
'CREATE TABLE statement_test (number integer not null)')
statement.stubs(:step).raises(SQLite3::BusyException, 'busy')
statement.stubs(:step).raises(::SQLite3::BusyException, 'busy')
statement.stubs(:columns).once.returns([])
statement.expects(:close).once
SQLite3::Statement.stubs(:new).returns(statement)
::SQLite3::Statement.stubs(:new).returns(statement)
assert_raises ActiveRecord::StatementInvalid do
@conn.exec_query 'select * from statement_test'
......
......@@ -81,7 +81,7 @@ def clear_log; self.log = []; self.log_all = []; end
oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im, /^\s*select .* from all_constraints/im, /^\s*select .* from all_tab_cols/im]
mysql_ignored = [/^SHOW TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /]
postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select tablename\b.*from pg_tables\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i]
sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im]
sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im, /^\s*SELECT sql\b.*\bFROM sqlite_master/im]
[oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql|
ignored_sql.concat db_ignored_sql
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册