typed_uuid 4.0.rc1 → 4.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 956d3f40e94a40968f9e66ae955a0eb4ca0a0d5e7c0a307e846a6bc329970108
4
- data.tar.gz: 49844d2082da751bc4d36dede69bdd927bb93269a8caadcaee20916a66e56c4f
3
+ metadata.gz: 581721d0683d1e3bf4bfa2c4bc29d4a7f5079854c85d98d8672e2f9bec6934e9
4
+ data.tar.gz: cb660ea624b3be55d02084ca8175035ade7774604a7428cb4555fa00e95460e6
5
5
  SHA512:
6
- metadata.gz: 5e4792f1bf817f6d3edf9b958ce843ddbda5d9fbb1286629a4cd91acdc56581d239e167b1808d580d5139233c1a74cc0aae3f88adc5585542413196735f6c7ea
7
- data.tar.gz: 143e55145e97eb7dd177d2d2785c03f8267cdad624a40cd52ce628c66194ae155c6d852b564eecdf991399e62c05ae1b48837698f362239aff6e2774a5b9ef1c
6
+ metadata.gz: 25835d74e2cf54a76f4be490f95fa98e5b0cbde8ddec6bafdd990a17d9a900fa7033f15f63a4be23609ae1164d686132a0472bec2e168d93f19e6204af1ab315
7
+ data.tar.gz: 8dcd77488ddd9f45bba84db7faec4b3e1f37c4258df3dc6273bee7eef1b620799cd725d56338759dc1d488aece3c32102a7364232e03183fa123cef481aa0543
data/README.md CHANGED
@@ -10,10 +10,10 @@ a hex representation of 4 bits.
10
10
  - M is 4 bits and is the Version
11
11
  - N is 3 bits and is the Variant of the Version followed a bit
12
12
 
13
- We modify this and use the following structure where the 9th & 10th bytes in the
14
- UUID are the enum XORed with the result of XORing bytes 7 & 8 with bytes 11 & 12.
13
+ We modify this and use the following structure where the 15th & 16th bytes in the
14
+ UUID are the enum XORed with the result of XORing bytes 5 & 6 with bytes 13 & 14.
15
15
 
16
- `xxxxxxxx-xxxx-YYYY-TTTT-ZZZZxxxxxxxx`
16
+ `xxxxxxxx-YYYY-xxxx-xxxx-xxxxZZZZTTTT`
17
17
 
18
18
  Where:
19
19
 
@@ -23,19 +23,20 @@ Where:
23
23
  - YYYY bytes XORed with ZZZZ and the Type ENUM to produce the identifying bytes
24
24
  - ZZZZ bytes XORed with YYYY and the Type ENUM to produce the identifying bytes
25
25
 
26
- XORing bytes 7 & 8 with 11 & 12 and XORing again with bytes 9 & 10 of the
26
+ XORing bytes 5 & 6 with 13 & 14 and XORing again with bytes 15 & 16 of the
27
27
  Typed UUID will give us back the ENUM and Version of the Type using soley the UUID.
28
28
 
29
29
  ## Versions
30
30
 
31
31
  As with regular UUID Typed UUIDs come in multiple version. The current versions are:
32
32
 
33
- - Version 1: A timebased UUID where the first 64 bits are an unsigned integer
34
- representing the nanoseconds since epoch. The following 16 bits are
35
- the UUID type folled by 48 random bits.
33
+ - Version 1: A timebased UUID where the first 56 bits are an unsigned integer
34
+ representing the microseconds since epoch. Followed by 48 random
35
+ bits or a sequence counter. Then 8 random bits followed by 16 bits
36
+ which are the UUID type.
36
37
 
37
- - Version 4: A random UUID where the first 64 bits are random. The following
38
- 16 bits are the UUID type folled by another 48 random bits.
38
+ - Version 4: A random UUID where the first 112 bits are random. The following
39
+ 16 bits are the UUID type.
39
40
 
40
41
  ## Install
41
42
 
@@ -81,10 +82,36 @@ class CreateProperties < ActiveRecord::Migration[5.2]
81
82
  end
82
83
  ```
83
84
 
85
+ To add a typed UUID to an existing table:
86
+
87
+ ```ruby
88
+ class UpdateProperties < ActiveRecord::Migration[6.1]
89
+ def change
90
+ klass_enum = ::ActiveRecord::Base.uuid_type_from_table_name(:properties)
91
+
92
+ # Add the column
93
+ add_column :properties, :typed_uuid, :uuid, default: -> { "typed_uuid('\\x#{klass_enum.to_s(16).rjust(4, '0')}')" }
94
+
95
+ # Update existing properties with a new typed UUID
96
+ execute "UPDATE properties SET id = typed_uuid('\\x#{klass_enum.to_s(16).rjust(4, '0')}');"
97
+
98
+ # Add null constraint since we'll swap these out for the primary key
99
+ change_column_null :properties, :typed_uuid, false
100
+
101
+ # TODO: Here you will want to update any reference to the old primary key
102
+ # with the new typed_uuid that will be the new primary key.
103
+
104
+ # Replace the old primary key with the typed_uuid
105
+ execute "ALTER TABLE properties DROP CONSTRAINT properties_pkey;"
106
+ rename_column :properties, :typed_uuid, :id
107
+ execute "ALTER TABLE properties ADD PRIMARY KEY (id);"
108
+ end
109
+ ```
110
+
84
111
  ## STI Models
85
112
  When using STI Model Rails will generate the UUID to be inserted. This UUID will
86
113
  be calculated of the STI Model class and not the base class.
87
114
 
88
115
  In the migration you can still used `id: :typed_uuid`, this will use the base
89
116
  class to calculated the default type for the UUID. You could also set the
90
- `id` to `:uuid` and the `default` to `false` so when no ID is given it will error.
117
+ `id` to `:uuid` and the `default` to `false` so when no ID is given it will error.
@@ -12,17 +12,16 @@ class AddTypedUuidFunction < ActiveRecord::Migration[6.0]
12
12
  BEGIN
13
13
  IF version = 1 THEN
14
14
  bytes := decode(concat(
15
- to_hex((extract(epoch from clock_timestamp())*1000000000)::bigint),
16
- encode(gen_random_bytes(8), 'hex')
15
+ lpad(right(to_hex((extract(epoch from clock_timestamp())*1000000)::bigint), 12), 12, '0'),
16
+ encode(gen_random_bytes(10), 'hex')
17
17
  ), 'hex');
18
18
  ELSE
19
19
  bytes := gen_random_bytes(16);
20
- version := 4;
21
20
  END IF;
22
21
 
23
22
  type := decode( lpad(to_hex(((enum << 3) | version)), 4, '0'), 'hex');
24
- bytes := set_byte(bytes, 8, (get_byte(bytes, 6) # get_byte(bytes, 10)) # get_byte(type, 0));
25
- bytes := set_byte(bytes, 9, (get_byte(bytes, 7) # get_byte(bytes, 11)) # get_byte(type, 1));
23
+ bytes := set_byte(bytes, 14, (get_byte(bytes, 4) # get_byte(bytes, 12)) # get_byte(type, 0));
24
+ bytes := set_byte(bytes, 15, (get_byte(bytes, 5) # get_byte(bytes, 13)) # get_byte(type, 1));
26
25
 
27
26
  RETURN encode( bytes, 'hex') :: uuid;
28
27
  END;
data/lib/typed_uuid.rb CHANGED
@@ -3,49 +3,50 @@ module TypedUUID
3
3
  autoload :PsqlColumnMethods, 'typed_uuid/psql_column_methods'
4
4
  autoload :PsqlSchemaDumper, 'typed_uuid/psql_schema_dumper'
5
5
 
6
- def self.uuid(enum, version = 4)
6
+ def self.uuid(enum, version = 4, **options)
7
7
  if enum < 0 || enum > 8_191
8
8
  raise ArgumentError, "UUID type must be between 0 and 8,191"
9
9
  end
10
10
 
11
11
  if version == 1
12
- timestamp_uuid(enum)
12
+ timestamp_uuid(enum, **options)
13
13
  elsif version == 4
14
- random_uuid(enum)
14
+ random_uuid(enum, **options)
15
15
  end
16
16
  end
17
17
 
18
18
  def self.random_uuid(enum)
19
19
  uuid = SecureRandom.random_bytes(16).unpack("nnnnnnnn")
20
20
 
21
- uuid[4] = (uuid[3] ^ uuid[5]) ^ ((enum << 3) | 4)
21
+ uuid[7] = (uuid[2] ^ uuid[6]) ^ ((enum << 3) | 4)
22
22
  "%04x%04x-%04x-%04x-%04x-%04x%04x%04x" % uuid
23
23
  end
24
-
25
- def self.timestamp_uuid(enum)
26
- time = Time.now
27
- uuid = [time.to_i * 1_000_000_000 + time.nsec].pack('Q>')
28
- uuid << SecureRandom.random_bytes(8)
29
-
24
+
25
+ def self.timestamp_uuid(enum, timestamp: nil, sequence: nil)
26
+ timestamp ||= Time.now
27
+
28
+ uuid = [timestamp.to_i * 1_000_000 + timestamp.usec].pack('Q>')[1..-1]
29
+ uuid << (sequence&.pack('Q>') || SecureRandom.random_bytes(10))
30
+
30
31
  uuid = uuid.unpack("nnnnnnnn")
31
- uuid[4] = (uuid[3] ^ uuid[5]) ^ ((enum << 3) | 1)
32
+ uuid[7] = (uuid[2] ^ uuid[6]) ^ ((enum << 3) | 1)
32
33
  "%04x%04x-%04x-%04x-%04x-%04x%04x%04x" % uuid
33
34
  end
34
35
 
35
36
  def self.enum(uuid)
36
37
  uuid = uuid.gsub('-', '')
37
- ((uuid[12..15].to_i(16) ^ uuid[20..23].to_i(16)) ^ uuid[16..19].to_i(16)) >> 3
38
+ ((uuid[8..11].to_i(16) ^ uuid[24..27].to_i(16)) ^ uuid[28..31].to_i(16)) >> 3
38
39
  end
39
40
 
40
41
  def self.version(uuid)
41
42
  uuid = uuid.gsub('-', '')
42
- ((uuid[12..15].to_i(16) ^ uuid[20..23].to_i(16)) ^ uuid[16..19].to_i(16)) & 0b0000000000000111
43
+ ((uuid[8..11].to_i(16) ^ uuid[24..27].to_i(16)) ^ uuid[28..31].to_i(16)) & 0b0000000000000111
43
44
  end
44
45
 
45
46
  def self.timestamp(uuid)
46
47
  uuid = uuid.gsub('-', '')
47
- Time.at(*uuid[0..15].to_i(16).divmod(1_000_000_000), :nsec)
48
+ Time.at(*uuid[0..13].to_i(16).divmod(1_000_000), :usec)
48
49
  end
49
50
  end
50
51
 
51
- require 'typed_uuid/railtie' if defined? Rails
52
+ require 'typed_uuid/railtie' if defined? Rails
@@ -64,8 +64,8 @@ module TypedUUID::ActiveRecord
64
64
  !!class_to_uuid_type_cache[self.base_class]
65
65
  end
66
66
 
67
- def typed_uuid
68
- TypedUUID.uuid(uuid_enum_from_class(self), uuid_version_from_class(self))
67
+ def typed_uuid(**options)
68
+ TypedUUID.uuid(uuid_enum_from_class(self), uuid_version_from_class(self), **options)
69
69
  end
70
70
 
71
71
  def uuid_enum_from_table_name(table)
@@ -6,15 +6,27 @@ module TypedUUID::PsqlSchemaDumper
6
6
  ## These are functions that must be enabled in order to support typed_uuids
7
7
  ## in this database
8
8
  execute <<-SQL
9
- CREATE OR REPLACE FUNCTION typed_uuid(t bytea) RETURNS uuid AS $$
10
- DECLARE
11
- bytes bytea := gen_random_bytes(16);
12
- uuid bytea;
13
- BEGIN
14
- bytes := set_byte(bytes, 6, (get_byte(bytes, 4) # get_byte(bytes, 8)) # get_byte(t, 0));
15
- bytes := set_byte(bytes, 7, (get_byte(bytes, 5) # get_byte(bytes, 9)) # get_byte(t, 1));
16
- RETURN encode( bytes, 'hex') :: uuid;
17
- END;
9
+ CREATE OR REPLACE FUNCTION typed_uuid(enum int, version int default 4)
10
+ RETURNS uuid AS $$
11
+ DECLARE
12
+ bytes bytea;
13
+ type bytea;
14
+ BEGIN
15
+ IF version = 1 THEN
16
+ bytes := decode(concat(
17
+ lpad(right(to_hex((extract(epoch from clock_timestamp())*1000000)::bigint), 12), 12, '0'),
18
+ encode(gen_random_bytes(10), 'hex')
19
+ ), 'hex');
20
+ ELSE
21
+ bytes := gen_random_bytes(16);
22
+ END IF;
23
+
24
+ type := decode( lpad(to_hex(((enum << 3) | version)), 4, '0'), 'hex');
25
+ bytes := set_byte(bytes, 14, (get_byte(bytes, 4) # get_byte(bytes, 12)) # get_byte(type, 0));
26
+ bytes := set_byte(bytes, 15, (get_byte(bytes, 5) # get_byte(bytes, 13)) # get_byte(type, 1));
27
+
28
+ RETURN encode( bytes, 'hex') :: uuid;
29
+ END;
18
30
  $$ LANGUAGE plpgsql;
19
31
  SQL
20
32
 
@@ -1,3 +1,3 @@
1
1
  module TypedUUID
2
- VERSION = '4.0.rc1'
2
+ VERSION = '4.0.rc2'
3
3
  end
data/test/test_helper.rb CHANGED
@@ -28,7 +28,9 @@ module ActiveRecord::Tasks::DatabaseTasks
28
28
  end
29
29
  TypedUUID::Railtie.initializers.each(&:run)
30
30
  Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
31
-
31
+ def MiniTest.filter_backtrace(bt)
32
+ bt
33
+ end
32
34
  class ActiveSupport::TestCase
33
35
 
34
36
  # File 'lib/active_support/testing/declarative.rb'
@@ -85,7 +85,7 @@ class FilterTest < ActiveSupport::TestCase
85
85
  end
86
86
 
87
87
  test 'typed_uuid v1' do
88
- time = Time.at(1606612254, 370979123, :nsec)
88
+ time = Time.at(1606612254, 370979, :usec)
89
89
  Time.stubs(:now).returns(time)
90
90
 
91
91
  uuid = TypedUUID.uuid(592, 1)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typed_uuid
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.rc1
4
+ version: 4.0.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Bracy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-30 00:00:00.000000000 Z
11
+ date: 2021-04-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -205,7 +205,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
205
205
  - !ruby/object:Gem::Version
206
206
  version: 1.3.1
207
207
  requirements: []
208
- rubygems_version: 3.1.4
208
+ rubygems_version: 3.2.3
209
209
  signing_key:
210
210
  specification_version: 4
211
211
  summary: Typed UUIDs for ActiveRecord