pg_random_id 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -1,5 +1,9 @@
1
1
  require "bundler/gem_tasks"
2
2
 
3
3
  require 'rspec/core/rake_task'
4
+ require 'ci/reporter/rake/rspec'
5
+
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
  task :default => :spec
8
+
9
+ task jenkins: ['ci:setup:rspec', :spec]
@@ -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
+ $$;
@@ -0,0 +1,7 @@
1
+ DROP FUNCTION pri_nextval(regclass);
2
+ DROP FUNCTION pri_nextval_str(regclass);
3
+
4
+ DROP TABLE pri_keys;
5
+
6
+ DROP FUNCTION crockford(input bigint);
7
+ DROP FUNCTION pri_scramble(key bigint, input bigint);
@@ -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, key = nil, base = nil
16
- key ||= rand(2**15)
17
- base ||= sequence_nextval "#{table}_#{column}_seq"
18
- "ALTER TABLE #{table} ALTER COLUMN #{column} SET DEFAULT pri_scramble(#{key}, #{base})"
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, base = nil
22
- base ||= sequence_nextval "#{table}_#{column}_seq"
21
+ def unapply table, column, options = {}
22
+ sequence = options[:sequence] || "#{table}_#{column}_seq"
23
23
  """
24
- ALTER TABLE #{table} ALTER COLUMN #{column} SET DEFAULT #{base};
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, key = nil, base = nil
30
- key ||= rand(2**15)
31
- base ||= sequence_nextval "#{table}_#{column}_seq"
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
- ALTER TABLE #{table} ALTER COLUMN #{column} SET DATA TYPE character(6);
34
- ALTER TABLE #{table} ALTER COLUMN #{column} SET DEFAULT lpad(crockford(pri_scramble(#{key}, #{base})), 6, '0');
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
@@ -1,3 +1,3 @@
1
1
  module PgRandomId
2
- VERSION = "0.2.0"
2
+ VERSION = "1.0.0"
3
3
  end
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/dividedmind/pg_random_id"
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
- %w(activerecord rspec sequel pg).each do |g|
21
- gem.add_development_dependency g
22
- end
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 '%pri_scramble%'").should be
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 '%pri_scramble%'").should_not be
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 '%pri_scramble%'").should_not be
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 '%pri_scramble%'").should be
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.2.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-10 00:00:00.000000000 Z
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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/dividedmind/pg_random_id
140
+ homepage: https://github.com/inscitiv/pg_random_id
107
141
  licenses: []
108
142
  post_install_message:
109
143
  rdoc_options: []