Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document and test with column collation #601

Merged
merged 2 commits into from
Feb 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/run_test_suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ jobs:
- name: run pg tests
env:
DB: pg
COLLATE_SYMBOLS: false
run: |
COUNT=1
while ! pg_isready ; do
Expand Down
32 changes: 20 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,24 +48,32 @@ $ rails g migration add_ancestry_to_[table] ancestry:string:index
# rails g migration add_name_to_[people] name:string:index
```

You will have best results to set the collation of the field. It works without
the collation, but without collation, it will not use the ancestry index. Alternatively
adding oppset will fix this.

Postgres on ubuntu in particular has been troublesome because by default,
it sorts ignoring slashes.


For postgres use `collate: :default`, and for mysql and sqllite use `collate: :binary`.

```ruby
class AddAncestryToTable < ActiveRecord::Migration[6.1]
def change
add_column :table, :ancestry, :string, collation: :default # alt: :binary
add_index :table, :ancestry
end
end
```

* Migrate your database:

```bash
$ rake db:migrate
```

Depending upon your comfort with databases, you may want to create the column
with `C` or `POSIX` encoding. This is a more primitive encoding and just compares
bytes. Since this column will just contain numbers and slashes, it works much
better. It also works better for the uuid case as well.

Alternatively, if you create a [`text_pattern_ops`](https://www.postgresql.org/docs/current/indexes-opclass.html) index for your postgresql column, subtree selection will use an efficient index for you regardless of whether you created the column with `POSIX` encoding.

If you opt out of this, and are trying to run tests on postgres, you may need to
set the environment variable `COLLATE_SYMBOLS=false`. Sorry to say that a discussion
on this topic is out of scope. The important take away is postgres sort order is
not consistent across operating systems but other databases do not have this same
issue.
NOTE: If you decide to add collation to this column at a later time, please remember to drop and recreate this index.

NOTE: A Btree index (as is recommended) has a limitaton of 2704 characters for the ancestry column. This means you can't have an tree with a depth that is too great (~> 900 items at most).

Expand Down
8 changes: 2 additions & 6 deletions test/concerns/scopes_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,8 @@ def test_chained_scopes
def test_order_by
AncestryTestDatabase.with_model :depth => 3, :width => 3 do |model, _roots|
# Some pg databases do not use symbols in sorting
# if this is failing, try running the test via DB=pg COLLATE_SYMBOLS=false rake test
if ENV["COLLATE_SYMBOLS"].to_s =~ /false/i
expected = model.all.sort_by { |m| [m.ancestor_ids.map(&:to_s).join, m.id.to_i] }
else
expected = model.all.sort_by { |m| [m.ancestor_ids.map(&:to_s), m.id.to_i] }
end
# if this is failing, try tweaking the collation of your ancestry columns
expected = model.all.sort_by { |m| [m.ancestor_ids.map(&:to_s), m.id.to_i] }
actual = model.ordered_by_ancestry_and(:id)
assert_equal (expected.map { |r| [r.ancestor_ids, r.id.to_s] }), (actual.map { |r| [r.ancestor_ids, r.id.to_s] })
end
Expand Down
22 changes: 20 additions & 2 deletions test/environment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def self.setup

# This only affects postgres
# the :ruby code path will get tested in mysql and sqlite3
Ancestry.default_update_strategy = :sql if db_type == "pg"
Ancestry.default_update_strategy = :sql if postgres?

rescue => err
if ENV["CI"]
Expand All @@ -69,6 +69,18 @@ def self.setup
end
end

# pass ANCESTRY_LOCALE=default to not override locale on ancestry
def self.ancestry_collation
env = ENV["ANCESTRY_LOCALE"].presence
if env
env
elsif postgres?
"C"
else
"binary"
end
end

def self.with_model options = {}
depth = options.delete(:depth) || 0
width = options.delete(:width) || 0
Expand All @@ -79,8 +91,11 @@ def self.with_model options = {}
table_options={}
table_options[:id] = options.delete(:id) if options.key?(:id)

column_options = {:collation => ancestry_collation}
column_options = {} if column_options[:collation] == "default"

ActiveRecord::Base.connection.create_table 'test_nodes', **table_options do |table|
table.string options[:ancestry_column] || :ancestry
table.string options[:ancestry_column] || :ancestry, **column_options
table.integer options[:depth_cache_column] || :ancestry_depth if options[:cache_depth]
if options[:counter_cache]
counter_cache_column = options[:counter_cache] == true ? :children_count : options[:counter_cache]
Expand Down Expand Up @@ -128,6 +143,9 @@ def self.create_test_nodes model, depth, width, parent = nil
else; []; end
end

def self.postgres?
db_type == "pg"
end
private

def self.db_type
Expand Down