Skip to content

Commit 8556ab5

Browse files
authored
Merge pull request rails#25395 from yawboakye/use_gen_random_uuid_from_pgcrypto_extension
For PostgreSQL >= 9.4 use `gen_random_uuid()`
2 parents 44d58cd + b915b11 commit 8556ab5

File tree

6 files changed

+86
-22
lines changed

6 files changed

+86
-22
lines changed

activerecord/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
* For PostgreSQL >= 9.4 use `pgcrypto`'s `gen_random_uuid()` instead of
2+
`uuid-ossp`'s UUID generation function.
3+
4+
*Yuji Yaginuma*, *Yaw Boakye*
5+
16
* Introduce `Model#reload_<association>` to bring back the behavior
27
of `Article.category(true)` where `category` is a singular
38
association.

activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,28 @@ module ColumnMethods
1111
# t.timestamps
1212
# end
1313
#
14-
# By default, this will use the +uuid_generate_v4()+ function from the
15-
# +uuid-ossp+ extension, which MUST be enabled on your database. To enable
16-
# the +uuid-ossp+ extension, you can use the +enable_extension+ method in your
17-
# migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can
18-
# set the +:default+ option to +nil+:
14+
# By default, this will use the +gen_random_uuid()+ function from the
15+
# +pgcrypto+ extension (only PostgreSQL >= 9.4), or +uuid_generate_v4()+
16+
# function from the +uuid-ossp+ extension. To enable the appropriate
17+
# extension, which is a requirement, you can use the +enable_extension+
18+
# method in your migrations. To use a UUID primary key without any of
19+
# of extensions, you can set the +:default+ option to +nil+:
1920
#
2021
# create_table :stuffs, id: false do |t|
2122
# t.primary_key :id, :uuid, default: nil
2223
# t.uuid :foo_id
2324
# t.timestamps
2425
# end
2526
#
26-
# You may also pass a different UUID generation function from +uuid-ossp+
27-
# or another library.
27+
# You may also pass a custom stored procedure that returns a UUID or use a
28+
# different UUID generation function from another library.
2829
#
2930
# Note that setting the UUID primary key default value to +nil+ will
3031
# require you to assure that you always provide a UUID value before saving
3132
# a record (as primary keys cannot be +nil+). This might be done via the
3233
# +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
3334
def primary_key(name, type = :primary_key, **options)
34-
options[:default] = options.fetch(:default, "uuid_generate_v4()") if type == :uuid
35+
options[:default] = options.fetch(:default, "gen_random_uuid()") if type == :uuid
3536
super
3637
end
3738

activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,10 @@ def supports_materialized_views?
315315
postgresql_version >= 90300
316316
end
317317

318+
def supports_pgcrypto_uuid?
319+
postgresql_version >= 90400
320+
end
321+
318322
def get_advisory_lock(lock_id) # :nodoc:
319323
unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
320324
raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer")

activerecord/lib/active_record/migration/compatibility.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@ def index_name_for_remove(table_name, options = {})
103103
end
104104

105105
class V5_0 < V5_1
106+
def create_table(table_name, options = {})
107+
if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
108+
if options[:id] == :uuid && !options[:default]
109+
options[:default] = "uuid_generate_v4()"
110+
end
111+
end
112+
super
113+
end
106114
end
107115

108116
class V4_2 < V5_0

activerecord/test/cases/adapters/postgresql/uuid_test.rb

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ def connection
99
def drop_table(name)
1010
connection.drop_table name, if_exists: true
1111
end
12+
13+
def uuid_function
14+
connection.supports_pgcrypto_uuid? ? "gen_random_uuid()" : "uuid_generate_v4()"
15+
end
1216
end
1317

1418
class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
@@ -21,6 +25,7 @@ class UUIDType < ActiveRecord::Base
2125

2226
setup do
2327
enable_extension!("uuid-ossp", connection)
28+
enable_extension!("pgcrypto", connection) if connection.supports_pgcrypto_uuid?
2429

2530
connection.create_table "uuid_data_type" do |t|
2631
t.uuid "guid"
@@ -31,19 +36,27 @@ class UUIDType < ActiveRecord::Base
3136
drop_table "uuid_data_type"
3237
end
3338

34-
def test_change_column_default
35-
@connection.add_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v1()"
36-
UUIDType.reset_column_information
37-
column = UUIDType.columns_hash["thingy"]
38-
assert_equal "uuid_generate_v1()", column.default_function
39-
40-
@connection.change_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v4()"
41-
42-
UUIDType.reset_column_information
43-
column = UUIDType.columns_hash["thingy"]
44-
assert_equal "uuid_generate_v4()", column.default_function
45-
ensure
46-
UUIDType.reset_column_information
39+
if ActiveRecord::Base.connection.supports_pgcrypto_uuid?
40+
def test_uuid_column_default
41+
connection.add_column :uuid_data_type, :thingy, :uuid, null: false, default: "gen_random_uuid()"
42+
UUIDType.reset_column_information
43+
column = UUIDType.columns_hash["thingy"]
44+
assert_equal "gen_random_uuid()", column.default_function
45+
end
46+
else
47+
def test_change_column_default
48+
connection.add_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v1()"
49+
UUIDType.reset_column_information
50+
column = UUIDType.columns_hash["thingy"]
51+
assert_equal "uuid_generate_v1()", column.default_function
52+
53+
connection.change_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v4()"
54+
UUIDType.reset_column_information
55+
column = UUIDType.columns_hash["thingy"]
56+
assert_equal "uuid_generate_v4()", column.default_function
57+
ensure
58+
UUIDType.reset_column_information
59+
end
4760
end
4861

4962
def test_data_type_of_uuid_types
@@ -155,7 +168,7 @@ class UUID < ActiveRecord::Base
155168
# to test dumping tables which columns have defaults with custom functions
156169
connection.execute <<-SQL
157170
CREATE OR REPLACE FUNCTION my_uuid_generator() RETURNS uuid
158-
AS $$ SELECT * FROM uuid_generate_v4() $$
171+
AS $$ SELECT * FROM #{uuid_function} $$
159172
LANGUAGE SQL VOLATILE;
160173
SQL
161174

@@ -164,11 +177,16 @@ class UUID < ActiveRecord::Base
164177
t.string "name"
165178
t.uuid "other_uuid_2", default: "my_uuid_generator()"
166179
end
180+
181+
connection.create_table("pg_uuids_3", id: :uuid) do |t|
182+
t.string "name"
183+
end
167184
end
168185

169186
teardown do
170187
drop_table "pg_uuids"
171188
drop_table "pg_uuids_2"
189+
drop_table "pg_uuids_3"
172190
connection.execute "DROP FUNCTION IF EXISTS my_uuid_generator();"
173191
end
174192

@@ -206,6 +224,33 @@ def test_schema_dumper_for_uuid_primary_key_with_custom_default
206224
assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: -> { "my_uuid_generator\(\)" }/, schema)
207225
assert_match(/t\.uuid "other_uuid_2", default: -> { "my_uuid_generator\(\)" }/, schema)
208226
end
227+
228+
def test_schema_dumper_for_uuid_primary_key_default
229+
schema = dump_table_schema "pg_uuids_3"
230+
if connection.supports_pgcrypto_uuid?
231+
assert_match(/\bcreate_table "pg_uuids_3", id: :uuid, default: -> { "gen_random_uuid\(\)" }/, schema)
232+
else
233+
assert_match(/\bcreate_table "pg_uuids_3", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema)
234+
end
235+
end
236+
237+
if ActiveRecord::Base.connection.supports_pgcrypto_uuid?
238+
def test_schema_dumper_for_uuid_primary_key_default_in_legacy_migration
239+
migration = Class.new(ActiveRecord::Migration[4.2]) do
240+
def version; 101 end
241+
def migrate(x)
242+
create_table("pg_uuids_4", id: :uuid)
243+
end
244+
end.new
245+
ActiveRecord::Migrator.new(:up, [migration]).migrate
246+
247+
schema = dump_table_schema "pg_uuids_4"
248+
assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema)
249+
ensure
250+
drop_table "pg_uuids_4"
251+
end
252+
else
253+
end
209254
end
210255
end
211256

activerecord/test/schema/postgresql_specific_schema.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
ActiveRecord::Schema.define do
22

33
enable_extension!("uuid-ossp", ActiveRecord::Base.connection)
4+
enable_extension!("pgcrypto", ActiveRecord::Base.connection) if ActiveRecord::Base.connection.supports_pgcrypto_uuid?
45

56
create_table :uuid_parents, id: :uuid, force: true do |t|
67
t.string :name

0 commit comments

Comments
 (0)