aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md12
-rw-r--r--activerecord/lib/active_record/associations.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb13
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb36
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb19
7 files changed, 86 insertions, 16 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index bfdc978005..477bc1b54a 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,15 @@
+* SQLite3 adapter supports expression indexes.
+
+ ```
+ create_table :users do |t|
+ t.string :email
+ end
+
+ add_index :users, 'lower(email)', name: 'index_users_on_email', unique: true
+ ```
+
+ *Gray Kemmey*
+
* Allow subclasses to redefine autosave callbacks for associated records.
Fixes #33305.
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 1ee52945ea..70b2ca7d7e 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1524,6 +1524,7 @@ module ActiveRecord
# Returns the associated object. +nil+ is returned if none is found.
# [association=(associate)]
# Assigns the associate object, extracts the primary key, and sets it as the foreign key.
+ # No modification or deletion of existing records takes place.
# [build_association(attributes = {})]
# Returns a new object of the associated type that has been instantiated
# with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 92f15de219..10c8c8e8ab 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -3,7 +3,7 @@
require "active_record/connection_adapters/abstract_mysql_adapter"
require "active_record/connection_adapters/mysql/database_statements"
-gem "mysql2", ">= 0.4.4", "< 0.6.0"
+gem "mysql2", ">= 0.4.4"
require "mysql2"
module ActiveRecord
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
index 24e7bc65fa..46ca7e07a9 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
@@ -21,19 +21,24 @@ module ActiveRecord
WHERE name = #{quote(row['name'])} AND type = 'index'
SQL
- /\sWHERE\s+(?<where>.+)$/i =~ index_sql
+ /\bON\b\s*"?(\w+?)"?\s*\((?<expressions>.+?)\)(?:\s*WHERE\b\s*(?<where>.+))?\z/i =~ index_sql
columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col|
col["name"]
end
- # Add info on sort order for columns (only desc order is explicitly specified, asc is
- # the default)
orders = {}
- if index_sql # index_sql can be null in case of primary key indexes
- index_sql.scan(/"(\w+)" DESC/).flatten.each { |order_column|
- orders[order_column] = :desc
- }
+
+ if columns.any?(&:nil?) # index created with an expression
+ columns = expressions
+ else
+ # Add info on sort order for columns (only desc order is explicitly specified,
+ # asc is the default)
+ if index_sql # index_sql can be null in case of primary key indexes
+ index_sql.scan(/"(\w+)" DESC/).flatten.each { |order_column|
+ orders[order_column] = :desc
+ }
+ end
end
IndexDefinition.new(
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index efe454fa7f..d32fd5ea09 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -125,6 +125,10 @@ module ActiveRecord
true
end
+ def supports_expression_index?
+ sqlite_version >= "3.9.0"
+ end
+
def requires_reloading?
true
end
@@ -483,9 +487,12 @@ module ActiveRecord
name = name[1..-1]
end
- to_column_names = columns(to).map(&:name)
- columns = index.columns.map { |c| rename[c] || c }.select do |column|
- to_column_names.include?(column)
+ columns = index.columns
+ if columns.is_a?(Array)
+ to_column_names = columns(to).map(&:name)
+ columns = columns.map { |c| rename[c] || c }.select do |column|
+ to_column_names.include?(column)
+ end
end
unless columns.empty?
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index d1d4d545a3..7b73621ed9 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -345,6 +345,42 @@ module ActiveRecord
end
end
+ if ActiveRecord::Base.connection.supports_expression_index?
+ def test_expression_index
+ with_example_table do
+ @conn.add_index "ex", "max(id, number)", name: "expression"
+ index = @conn.indexes("ex").find { |idx| idx.name == "expression" }
+ assert_equal "max(id, number)", index.columns
+ end
+ end
+
+ def test_expression_index_with_where
+ with_example_table do
+ @conn.add_index "ex", "id % 10, max(id, number)", name: "expression", where: "id > 1000"
+ index = @conn.indexes("ex").find { |idx| idx.name == "expression" }
+ assert_equal "id % 10, max(id, number)", index.columns
+ assert_equal "id > 1000", index.where
+ end
+ end
+
+ def test_complicated_expression
+ with_example_table do
+ @conn.execute "CREATE INDEX expression ON ex (id % 10, (CASE WHEN number > 0 THEN max(id, number) END))WHERE(id > 1000)"
+ index = @conn.indexes("ex").find { |idx| idx.name == "expression" }
+ assert_equal "id % 10, (CASE WHEN number > 0 THEN max(id, number) END)", index.columns
+ assert_equal "(id > 1000)", index.where
+ end
+ end
+
+ def test_not_everything_an_expression
+ with_example_table do
+ @conn.add_index "ex", "id, max(id, number)", name: "expression"
+ index = @conn.indexes("ex").find { |idx| idx.name == "expression" }
+ assert_equal "id, max(id, number)", index.columns
+ end
+ end
+ end
+
def test_primary_key
with_example_table do
assert_equal "id", @conn.primary_key("ex")
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 75b68b521c..db13f20a39 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -226,6 +226,20 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t\.float\s+"temperature"$}, output
end
+ if ActiveRecord::Base.connection.supports_expression_index?
+ def test_schema_dump_expression_indices
+ index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_expression_index/).first.strip
+
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_match %r{CASE.+lower\(\(name\)::text\)}i, index_definition
+ elsif current_adapter?(:SQLite3Adapter)
+ assert_match %r{CASE.+lower\(name\)}i, index_definition
+ else
+ assert false
+ end
+ end
+ end
+
if current_adapter?(:Mysql2Adapter)
def test_schema_dump_includes_length_for_mysql_binary_fields
output = standard_dump
@@ -278,11 +292,6 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t\.decimal\s+"decimal_array_default",\s+default: \["1.23", "3.45"\],\s+array: true}, output
end
- def test_schema_dump_expression_indices
- index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_expression_index/).first.strip
- assert_match %r{CASE.+lower\(\(name\)::text\)}i, index_definition
- end
-
def test_schema_dump_interval_type
output = dump_table_schema "postgresql_times"
assert_match %r{t\.interval\s+"time_interval"$}, output