pg_random_id 0.2.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +49 -0
- data/Rakefile +4 -0
- data/lib/pg_random_id/migrations.rb +2 -2
- data/lib/pg_random_id/sql/keytable.sql +27 -0
- data/lib/pg_random_id/sql/uninstall.sql +7 -0
- data/lib/pg_random_id/sql.rb +20 -21
- data/lib/pg_random_id/version.rb +1 -1
- data/pg_random_id.gemspec +7 -4
- data/spec/helpers/migration_spec_helper.rb +5 -5
- metadata +53 -19
data/README.md
CHANGED
|
@@ -7,6 +7,7 @@ or 6-character human-friendly-ish strings (eg. kn5xx1, qy2kp8, e5f67z...).
|
|
|
7
7
|
|
|
8
8
|
Since surrogate IDs are often used in REST-ful URLs, this makes the addresses less revealing and harder to guess
|
|
9
9
|
(while preserving the straightforward mapping from URLs to database IDs):
|
|
10
|
+
|
|
10
11
|
- http://example.com/products/1 → http://example.com/products/134178313
|
|
11
12
|
- http://example.com/products/2 → http://example.com/products/121521131
|
|
12
13
|
- http://example.com/widgets/1 → http://example.com/widgets/2agc30
|
|
@@ -119,13 +120,40 @@ Sequel.migration do
|
|
|
119
120
|
end
|
|
120
121
|
```
|
|
121
122
|
|
|
123
|
+
## Upgrading
|
|
124
|
+
|
|
125
|
+
If you want to upgrade a live database from before 1.0.0 version, you need to call
|
|
126
|
+
create_random_id_functions in a migration again, as key storage changed
|
|
127
|
+
from being in the default value to a separate key table to allow prefetching values.
|
|
128
|
+
|
|
129
|
+
Note that existing random ids will still use the old format and you won't be able
|
|
130
|
+
to use `pri_nextval` on them, though they'll continue to work.
|
|
131
|
+
|
|
132
|
+
If you don't have a live database to upgrade, you don't need to do anything.
|
|
133
|
+
Migration methods haven't changed and will work using the new format;
|
|
134
|
+
just make sure to recreate schema.sql if you're using it.
|
|
135
|
+
|
|
136
|
+
## Prefetching values
|
|
137
|
+
|
|
138
|
+
Installing pg_random_id in the database allows using `pri_nextval` and `pri_nextval_str`
|
|
139
|
+
SQL functions in much the same manner as the standard `nextval` function, eg.:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
next = DB["SELECT pri_nextval('foo_id_seq'::regclass)"].first.values[0]
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Note that this feature is only available since version 1.0.0.
|
|
146
|
+
|
|
122
147
|
## Considerations
|
|
123
148
|
|
|
124
149
|
No model modification is necessary, just use the table as usual and it will simply work.
|
|
125
150
|
Each table will use its own unique sequence, chosen at random at migration time.
|
|
126
151
|
|
|
152
|
+
### Foreign keys
|
|
153
|
+
|
|
127
154
|
If you use `random_str_id` make sure to use a string type in
|
|
128
155
|
foreign key columns:
|
|
156
|
+
|
|
129
157
|
```ruby
|
|
130
158
|
class CreateContraptions < ActiveRecord::Migration
|
|
131
159
|
def up
|
|
@@ -135,6 +163,7 @@ class CreateContraptions < ActiveRecord::Migration
|
|
|
135
163
|
end
|
|
136
164
|
end
|
|
137
165
|
```
|
|
166
|
+
|
|
138
167
|
```ruby
|
|
139
168
|
Sequel.migration do
|
|
140
169
|
up do
|
|
@@ -145,6 +174,26 @@ Sequel.migration do
|
|
|
145
174
|
end
|
|
146
175
|
```
|
|
147
176
|
|
|
177
|
+
### Non-serial columns
|
|
178
|
+
|
|
179
|
+
Both these functions are meant to replace standard serial columns, which means they expect a `#{table}_#{column}_seq`
|
|
180
|
+
sequence to be present. It is automatically created by Postgres when you create a `serial` column (which is what AR
|
|
181
|
+
implicitly does on `create_table` unless you say `id: false`, and what Sequel does when you say `primary_key :id`),
|
|
182
|
+
but if you're not doing this, make sure to create the sequence, like so:
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
create_table :groups do
|
|
186
|
+
String :id, primary_key: true
|
|
187
|
+
String :name, null: false
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
execute "CREATE SEQUENCE groups_id_seq OWNED BY groups.id"
|
|
191
|
+
random_str_id :groups # changes id type to char(6)
|
|
192
|
+
|
|
193
|
+
# Change type back to varchar if you want to be able to use longer ids (ie. not autogenerated)
|
|
194
|
+
execute "ALTER TABLE group ALTER COLUMN id SET DATA TYPE varchar"
|
|
195
|
+
```
|
|
196
|
+
|
|
148
197
|
## Notes
|
|
149
198
|
|
|
150
199
|
The `random_id` function changes the default value of the ID column to a scrambled next sequence value.
|
data/Rakefile
CHANGED
|
@@ -18,7 +18,7 @@ module PgRandomId
|
|
|
18
18
|
# The ids will be based on sequence "#{table}_#{column}_seq".
|
|
19
19
|
# You need to make sure the table is empty; migrating existing records is not implemented.
|
|
20
20
|
def random_id table, column = :id, key = nil
|
|
21
|
-
execute PgRandomId::Sql::apply(table, column, key)
|
|
21
|
+
execute PgRandomId::Sql::apply(table, column, key: key)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# Changes type of a column to int and restores sequence default on it.
|
|
@@ -38,7 +38,7 @@ module PgRandomId
|
|
|
38
38
|
# scrambled and base32-encoded.
|
|
39
39
|
# You need to make sure the table is empty; migrating existing records is not implemented.
|
|
40
40
|
def random_str_id table, column = :id, key = nil
|
|
41
|
-
execute PgRandomId::Sql::apply_str(table, column, key)
|
|
41
|
+
execute PgRandomId::Sql::apply_str(table, column, key: key)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
# Install the migration functions for ActiveRecord
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
SET LOCAL client_min_messages = error; -- to avoid implicit index messages
|
|
2
|
+
|
|
3
|
+
CREATE TABLE pri_keys (
|
|
4
|
+
sequence regclass PRIMARY KEY,
|
|
5
|
+
key integer NOT NULL);
|
|
6
|
+
|
|
7
|
+
CREATE OR REPLACE FUNCTION
|
|
8
|
+
pri_nextval(sequence regclass) RETURNS integer
|
|
9
|
+
LANGUAGE sql
|
|
10
|
+
VOLATILE
|
|
11
|
+
STRICT
|
|
12
|
+
AS $$
|
|
13
|
+
SELECT pri_scramble(key, nextval($1))
|
|
14
|
+
FROM pri_keys
|
|
15
|
+
WHERE sequence = $1;
|
|
16
|
+
$$;
|
|
17
|
+
|
|
18
|
+
CREATE OR REPLACE FUNCTION
|
|
19
|
+
pri_nextval_str(sequence regclass) RETURNS char(6)
|
|
20
|
+
LANGUAGE sql
|
|
21
|
+
VOLATILE
|
|
22
|
+
STRICT
|
|
23
|
+
AS $$
|
|
24
|
+
SELECT lpad(crockford(pri_scramble(key, nextval($1))), 6, '0')
|
|
25
|
+
FROM pri_keys
|
|
26
|
+
WHERE sequence = $1;
|
|
27
|
+
$$;
|
data/lib/pg_random_id/sql.rb
CHANGED
|
@@ -6,47 +6,46 @@ module PgRandomId
|
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
def uninstall
|
|
9
|
-
|
|
10
|
-
DROP FUNCTION crockford(input bigint);
|
|
11
|
-
DROP FUNCTION pri_scramble(key bigint, input bigint);
|
|
12
|
-
"""
|
|
9
|
+
read_file 'uninstall.sql'
|
|
13
10
|
end
|
|
14
11
|
|
|
15
|
-
def apply table, column,
|
|
16
|
-
key
|
|
17
|
-
|
|
18
|
-
"
|
|
12
|
+
def apply table, column, options = {}
|
|
13
|
+
key = options[:key] || rand(2**15)
|
|
14
|
+
sequence = options[:sequence] || "#{table}_#{column}_seq"
|
|
15
|
+
"""
|
|
16
|
+
INSERT INTO pri_keys VALUES ('#{sequence}'::regclass, #{key});
|
|
17
|
+
ALTER TABLE #{table} ALTER COLUMN #{column} SET DEFAULT pri_nextval('#{sequence}'::regclass);
|
|
18
|
+
"""
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
def unapply table, column,
|
|
22
|
-
|
|
21
|
+
def unapply table, column, options = {}
|
|
22
|
+
sequence = options[:sequence] || "#{table}_#{column}_seq"
|
|
23
23
|
"""
|
|
24
|
-
ALTER TABLE #{table} ALTER COLUMN #{column} SET DEFAULT #{
|
|
24
|
+
ALTER TABLE #{table} ALTER COLUMN #{column} SET DEFAULT nextval('#{sequence}'::regclass);
|
|
25
25
|
ALTER TABLE #{table} ALTER COLUMN #{column} SET DATA TYPE integer USING 0;
|
|
26
|
+
DELETE FROM pri_keys WHERE sequence = '#{sequence}'::regclass;
|
|
26
27
|
"""
|
|
27
28
|
end
|
|
28
29
|
|
|
29
|
-
def apply_str table, column,
|
|
30
|
-
key
|
|
31
|
-
|
|
30
|
+
def apply_str table, column, options = {}
|
|
31
|
+
key = options[:key] || rand(2**15)
|
|
32
|
+
sequence = options[:sequence] || "#{table}_#{column}_seq"
|
|
33
|
+
type = options[:type] || "character(6)"
|
|
32
34
|
"""
|
|
33
|
-
|
|
34
|
-
ALTER TABLE #{table} ALTER COLUMN #{column} SET
|
|
35
|
+
INSERT INTO pri_keys VALUES ('#{sequence}'::regclass, #{key});
|
|
36
|
+
ALTER TABLE #{table} ALTER COLUMN #{column} SET DATA TYPE #{type};
|
|
37
|
+
ALTER TABLE #{table} ALTER COLUMN #{column} SET DEFAULT pri_nextval_str('#{sequence}'::regclass);
|
|
35
38
|
"""
|
|
36
39
|
end
|
|
37
40
|
|
|
38
41
|
private
|
|
39
42
|
|
|
40
|
-
FILES = %w(scramble.sql crockford.sql)
|
|
43
|
+
FILES = %w(scramble.sql crockford.sql keytable.sql)
|
|
41
44
|
BASEDIR = File.expand_path 'sql', File.dirname(__FILE__)
|
|
42
45
|
|
|
43
46
|
def read_file filename
|
|
44
47
|
File.read(File.expand_path(filename, BASEDIR))
|
|
45
48
|
end
|
|
46
|
-
|
|
47
|
-
def sequence_nextval sequence_name
|
|
48
|
-
"nextval('#{sequence_name}'::regclass)"
|
|
49
|
-
end
|
|
50
49
|
end
|
|
51
50
|
end
|
|
52
51
|
end
|
data/lib/pg_random_id/version.rb
CHANGED
data/pg_random_id.gemspec
CHANGED
|
@@ -10,14 +10,17 @@ Gem::Specification.new do |gem|
|
|
|
10
10
|
gem.email = ["divided.mind@gmail.com"]
|
|
11
11
|
gem.description = %q{Easily use randomized keys instead of sequential values for your record surrogate ids.}
|
|
12
12
|
gem.summary = %q{Pseudo-random record ids in Postgres}
|
|
13
|
-
gem.homepage = "https://github.com/
|
|
13
|
+
gem.homepage = "https://github.com/inscitiv/pg_random_id"
|
|
14
14
|
|
|
15
15
|
gem.files = `git ls-files`.split($/)
|
|
16
16
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
17
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
18
18
|
gem.require_paths = ["lib"]
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
gem.add_development_dependency 'activerecord', '~>3.2'
|
|
21
|
+
gem.add_development_dependency 'rspec', '~>2.12'
|
|
22
|
+
gem.add_development_dependency 'sequel', '~>3.44'
|
|
23
|
+
gem.add_development_dependency 'pg', '~>0.14'
|
|
24
|
+
gem.add_development_dependency 'ci_reporter', '~>1.8'
|
|
25
|
+
gem.add_development_dependency 'rake', '~>10.0'
|
|
23
26
|
end
|
|
@@ -23,7 +23,7 @@ shared_context 'test_migration' do
|
|
|
23
23
|
migration.create_random_id_functions
|
|
24
24
|
create_table :foo
|
|
25
25
|
migration.random_id :foo
|
|
26
|
-
execute("SELECT 1 FROM pg_attrdef WHERE adrelid = 'foo'::regclass AND adsrc LIKE '%
|
|
26
|
+
execute("SELECT 1 FROM pg_attrdef WHERE adrelid = 'foo'::regclass AND adsrc LIKE '%pri_%'").should be
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
it "creates a few values without error" do
|
|
@@ -36,6 +36,7 @@ shared_context 'test_migration' do
|
|
|
36
36
|
}.to_not raise_error
|
|
37
37
|
end
|
|
38
38
|
execute("SELECT COUNT(*) FROM foo").first[1].to_i.should == 10
|
|
39
|
+
execute("SELECT id FROM foo LIMIT 1").first[1].to_i.should_not == 1
|
|
39
40
|
end
|
|
40
41
|
end
|
|
41
42
|
|
|
@@ -45,7 +46,7 @@ shared_context 'test_migration' do
|
|
|
45
46
|
create_table :foo
|
|
46
47
|
migration.random_id :foo
|
|
47
48
|
migration.remove_random_id :foo
|
|
48
|
-
execute("SELECT 1 FROM pg_attrdef WHERE adrelid = 'foo'::regclass AND adsrc LIKE '%
|
|
49
|
+
execute("SELECT 1 FROM pg_attrdef WHERE adrelid = 'foo'::regclass AND adsrc LIKE '%pri_%'").should_not be
|
|
49
50
|
execute("INSERT INTO foo VALUES (DEFAULT) RETURNING id;").first[1].to_i.should == 1
|
|
50
51
|
end
|
|
51
52
|
|
|
@@ -54,7 +55,7 @@ shared_context 'test_migration' do
|
|
|
54
55
|
create_table :foo
|
|
55
56
|
migration.random_str_id :foo
|
|
56
57
|
migration.remove_random_id :foo
|
|
57
|
-
execute("SELECT 1 FROM pg_attrdef WHERE adrelid = 'foo'::regclass AND adsrc LIKE '%
|
|
58
|
+
execute("SELECT 1 FROM pg_attrdef WHERE adrelid = 'foo'::regclass AND adsrc LIKE '%pri_%'").should_not be
|
|
58
59
|
execute("INSERT INTO foo VALUES (DEFAULT) RETURNING id;").first[1].to_i.should == 1
|
|
59
60
|
end
|
|
60
61
|
end
|
|
@@ -64,8 +65,7 @@ shared_context 'test_migration' do
|
|
|
64
65
|
migration.create_random_id_functions
|
|
65
66
|
create_table :foo
|
|
66
67
|
migration.random_str_id :foo
|
|
67
|
-
execute("SELECT 1 FROM pg_attrdef WHERE adrelid = 'foo'::regclass AND adsrc LIKE '%
|
|
68
|
-
execute("SELECT 1 FROM pg_attrdef WHERE adrelid = 'foo'::regclass AND adsrc LIKE '%crockford%'").should be
|
|
68
|
+
execute("SELECT 1 FROM pg_attrdef WHERE adrelid = 'foo'::regclass AND adsrc LIKE '%pri_%'").should be
|
|
69
69
|
execute("INSERT INTO foo VALUES (DEFAULT) RETURNING id;").first[1].should_not == '1'
|
|
70
70
|
end
|
|
71
71
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pg_random_id
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,72 +9,104 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2013-02-
|
|
12
|
+
date: 2013-02-20 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: activerecord
|
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
|
17
17
|
none: false
|
|
18
18
|
requirements:
|
|
19
|
-
- -
|
|
19
|
+
- - ~>
|
|
20
20
|
- !ruby/object:Gem::Version
|
|
21
|
-
version: '
|
|
21
|
+
version: '3.2'
|
|
22
22
|
type: :development
|
|
23
23
|
prerelease: false
|
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
25
25
|
none: false
|
|
26
26
|
requirements:
|
|
27
|
-
- -
|
|
27
|
+
- - ~>
|
|
28
28
|
- !ruby/object:Gem::Version
|
|
29
|
-
version: '
|
|
29
|
+
version: '3.2'
|
|
30
30
|
- !ruby/object:Gem::Dependency
|
|
31
31
|
name: rspec
|
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
|
33
33
|
none: false
|
|
34
34
|
requirements:
|
|
35
|
-
- -
|
|
35
|
+
- - ~>
|
|
36
36
|
- !ruby/object:Gem::Version
|
|
37
|
-
version: '
|
|
37
|
+
version: '2.12'
|
|
38
38
|
type: :development
|
|
39
39
|
prerelease: false
|
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
|
41
41
|
none: false
|
|
42
42
|
requirements:
|
|
43
|
-
- -
|
|
43
|
+
- - ~>
|
|
44
44
|
- !ruby/object:Gem::Version
|
|
45
|
-
version: '
|
|
45
|
+
version: '2.12'
|
|
46
46
|
- !ruby/object:Gem::Dependency
|
|
47
47
|
name: sequel
|
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
|
49
49
|
none: false
|
|
50
50
|
requirements:
|
|
51
|
-
- -
|
|
51
|
+
- - ~>
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '
|
|
53
|
+
version: '3.44'
|
|
54
54
|
type: :development
|
|
55
55
|
prerelease: false
|
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
|
57
57
|
none: false
|
|
58
58
|
requirements:
|
|
59
|
-
- -
|
|
59
|
+
- - ~>
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
61
|
+
version: '3.44'
|
|
62
62
|
- !ruby/object:Gem::Dependency
|
|
63
63
|
name: pg
|
|
64
64
|
requirement: !ruby/object:Gem::Requirement
|
|
65
65
|
none: false
|
|
66
66
|
requirements:
|
|
67
|
-
- -
|
|
67
|
+
- - ~>
|
|
68
68
|
- !ruby/object:Gem::Version
|
|
69
|
-
version: '0'
|
|
69
|
+
version: '0.14'
|
|
70
70
|
type: :development
|
|
71
71
|
prerelease: false
|
|
72
72
|
version_requirements: !ruby/object:Gem::Requirement
|
|
73
73
|
none: false
|
|
74
74
|
requirements:
|
|
75
|
-
- -
|
|
75
|
+
- - ~>
|
|
76
76
|
- !ruby/object:Gem::Version
|
|
77
|
-
version: '0'
|
|
77
|
+
version: '0.14'
|
|
78
|
+
- !ruby/object:Gem::Dependency
|
|
79
|
+
name: ci_reporter
|
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
|
81
|
+
none: false
|
|
82
|
+
requirements:
|
|
83
|
+
- - ~>
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: '1.8'
|
|
86
|
+
type: :development
|
|
87
|
+
prerelease: false
|
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
89
|
+
none: false
|
|
90
|
+
requirements:
|
|
91
|
+
- - ~>
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '1.8'
|
|
94
|
+
- !ruby/object:Gem::Dependency
|
|
95
|
+
name: rake
|
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
|
97
|
+
none: false
|
|
98
|
+
requirements:
|
|
99
|
+
- - ~>
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '10.0'
|
|
102
|
+
type: :development
|
|
103
|
+
prerelease: false
|
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
105
|
+
none: false
|
|
106
|
+
requirements:
|
|
107
|
+
- - ~>
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '10.0'
|
|
78
110
|
description: Easily use randomized keys instead of sequential values for your record
|
|
79
111
|
surrogate ids.
|
|
80
112
|
email:
|
|
@@ -93,7 +125,9 @@ files:
|
|
|
93
125
|
- lib/pg_random_id/sql.rb
|
|
94
126
|
- lib/pg_random_id/sql/crockford-pure.sql
|
|
95
127
|
- lib/pg_random_id/sql/crockford.sql
|
|
128
|
+
- lib/pg_random_id/sql/keytable.sql
|
|
96
129
|
- lib/pg_random_id/sql/scramble.sql
|
|
130
|
+
- lib/pg_random_id/sql/uninstall.sql
|
|
97
131
|
- lib/pg_random_id/version.rb
|
|
98
132
|
- pg_random_id.gemspec
|
|
99
133
|
- spec/helpers/active_record_helper.rb
|
|
@@ -103,7 +137,7 @@ files:
|
|
|
103
137
|
- spec/spec_helper.rb
|
|
104
138
|
- spec/sql/crockford_spec.rb
|
|
105
139
|
- spec/sql/scramble_spec.rb
|
|
106
|
-
homepage: https://github.com/
|
|
140
|
+
homepage: https://github.com/inscitiv/pg_random_id
|
|
107
141
|
licenses: []
|
|
108
142
|
post_install_message:
|
|
109
143
|
rdoc_options: []
|