├── .gitignore ├── Gemfile ├── Guardfile ├── LICENSE ├── README.rdoc ├── Rakefile ├── lib ├── pg_sequencer.rb └── pg_sequencer │ ├── connection_adapters │ └── postgresql_adapter.rb │ ├── railtie.rb │ ├── schema_dumper.rb │ └── version.rb ├── pg_sequencer.gemspec └── test ├── helper.rb └── unit ├── postgresql_adapter_test.rb └── schema_dumper_test.rb /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | .bundle/ 3 | log/*.log 4 | pkg/ 5 | test/dummy/db/*.sqlite3 6 | test/dummy/log/*.log 7 | test/dummy/tmp/ 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Declare your gem's dependencies in pg_sequencer.gemspec. 4 | # Bundler will treat runtime dependencies like base dependencies, and 5 | # development dependencies will be added by default to the :development group. 6 | gemspec 7 | 8 | # Declare any dependencies that are still in development here instead of in 9 | # your gemspec. These might include edge Rails or gems from your path or 10 | # Git. Remember to move these dependencies to your gemspec before releasing 11 | # your gem to rubygems.org. 12 | 13 | # To use debugger 14 | # gem 'ruby-debug19', :require => 'ruby-debug' -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | guard 'test' do 5 | watch(%r{^lib/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" } 6 | watch(%r{^test/.+_test\.rb$}) 7 | watch('test/helper.rb') { "test" } 8 | end 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Code42, Inc. 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = NOTICE: Project is no longer maintained 2 | As of November 7th, 2018, Code42 has chosen to no longer actively host this project. The project was archive at this point. All issues have been closed and pull requests rejected. While archived, the source is still available, but this project may be permanantly removed from GitHub in the near future. 3 | 4 | = pg_sequencer 5 | 6 | pg_sequencer adds methods to your migrations to allow you to create, drop and change sequence objects in PostgreSQL. It also dumps sequences to schema.rb. 7 | 8 | This is especially useful if you are connecting to a legacy database where the primary key field is declared as an INTEGER and a sequence is queried for the value of the next record. 9 | 10 | The design of pg_sequencer is heavily influenced on Matthew Higgins' Foreigner gem: 11 | * https://github.com/matthuhiggins/foreigner 12 | 13 | == Installation 14 | 15 | Add this to your Gemfile: 16 | 17 | gem 'pg_sequencer' 18 | 19 | == API 20 | 21 | pg_sequencer adds the following methods to migrations: 22 | 23 | * create_sequence(sequence_name, options) 24 | * change_sequence(sequence_name, options) 25 | * drop_sequence(sequence_name) 26 | 27 | The methods closely mimic the syntax of the PostgreSQL SQL for CREATE SEQUENCE, DROP SEQUENCE and ALTER SEQUENCE. See the REFERENCES section below for more information. 28 | 29 | == Options 30 | 31 | For create_sequence and change_sequence, all options are the same, except create_sequence will look for :start or :start_with, and 32 | change_sequence will look for :restart or :restart_with. 33 | 34 | * :increment/:increment_by (integer) - The value to increment the sequence by. 35 | * :min (integer/false) - The minimum value of the sequence. If specified as false (e.g. :min => false), "NO MINVALUE" is sent to Postgres. 36 | * :max (integer/false) - The maximum value of the sequence. May be specified as ":max => false" to generate "NO MAXVALUE" 37 | * :start/:start_with (integer) - The starting value of the sequence (create_sequence only) 38 | * :restart/:restart_with (integer) The value to restart the sequence with (change_sequence only) 39 | * :cache (integer) - The number of values the sequence should cache. 40 | * :cycle (boolean) - Whether the sequence should cycle. Generated at "CYCLE" or "NO CYCLE" 41 | 42 | == Examples 43 | 44 | === Creating a sequence 45 | 46 | Create a sequence called "seq_user", incrementing by 1, min of 1, max of 2000000, starts at 1, caches 10 values, and disallows cycles: 47 | 48 | create_sequence("seq_user", { 49 | :increment => 1, 50 | :min => 1, 51 | :max => 2000000, 52 | :start => 1, 53 | :cache => 10, 54 | :cycle => false 55 | }) 56 | 57 | This is equivalent of the following query: 58 | 59 | CREATE SEQUENCE seq_user INCREMENT BY 1 MIN 1 MAX 2000000 START 1 CACHE 10 NO CYCLE 60 | 61 | === Reset a sequence's value: 62 | 63 | change_sequence "seq_accounts", :restart_with => 50 64 | 65 | This is equivalent to: 66 | 67 | ALTER SEQUENCE seq_accounts RESTART WITH 50 68 | 69 | === Removing a sequence: 70 | 71 | drop_sequence "seq_products" 72 | 73 | == Caveats / Bugs 74 | * Tested with postgres 9.0.4, should work down to 8.1. 75 | * Listing all the sequences in a database creates n+1 queries (1 to get the names and n to describe each sequence). Is there a way to fully describe all sequences in a database in one query? 76 | * The "SET SCHEMA" fragment of the ALTER command is not implemented. 77 | * Oracle/other databases not supported 78 | * Other unknown bugs :) 79 | 80 | == References 81 | * http://www.postgresql.org/docs/8.1/static/sql-createsequence.html 82 | * http://www.postgresql.org/docs/8.1/static/sql-altersequence.html 83 | * http://www.alberton.info/postgresql_meta_info.html 84 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Code42, Inc. 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | require 'rake' 21 | # begin 22 | # require 'bundler/setup' 23 | # rescue LoadError 24 | # puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 25 | # end 26 | 27 | desc 'Default: run unit tests.' 28 | task :default => :test 29 | 30 | require 'rake/testtask' 31 | desc 'Test the foreigner plugin.' 32 | Rake::TestTask.new(:test) do |t| 33 | t.libs << 'lib' 34 | t.libs << 'test' 35 | t.pattern = 'test/**/*_test.rb' 36 | t.verbose = true 37 | end 38 | -------------------------------------------------------------------------------- /lib/pg_sequencer.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Code42, Inc. 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | require 'active_support/all' 21 | 22 | module PgSequencer 23 | extend ActiveSupport::Autoload 24 | autoload :SchemaDumper 25 | 26 | module ConnectionAdapters 27 | end 28 | end 29 | 30 | require 'pg_sequencer/railtie' if defined?(Rails) 31 | -------------------------------------------------------------------------------- /lib/pg_sequencer/connection_adapters/postgresql_adapter.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Code42, Inc. 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | module PgSequencer 21 | module ConnectionAdapters 22 | 23 | class SequenceDefinition < Struct.new(:name, :options) 24 | end 25 | 26 | module PostgreSQLAdapter 27 | def create_sequence(name, options = {}) 28 | execute create_sequence_sql(name, options) 29 | end 30 | 31 | def drop_sequence(name) 32 | execute drop_sequence_sql(name) 33 | end 34 | 35 | def change_sequence(name, options = {}) 36 | execute change_sequence_sql(name, options) 37 | end 38 | 39 | # CREATE [ TEMPORARY | TEMP ] SEQUENCE name [ INCREMENT [ BY ] increment ] 40 | # [ MINVALUE minvalue | NO MINVALUE ] [ MAXVALUE maxvalue | NO MAXVALUE ] 41 | # [ START [ WITH ] start ] [ CACHE cache ] [ [ NO ] CYCLE ] 42 | # 43 | # create_sequence "seq_user", 44 | # :increment => 1, 45 | # :min => (1|false), 46 | # :max => (20000|false), 47 | # :start => 1, 48 | # :cache => 5, 49 | # :cycle => true 50 | def create_sequence_sql(name, options = {}) 51 | options.delete(:restart) 52 | "CREATE SEQUENCE #{name}#{sequence_options_sql(options)}" 53 | end 54 | 55 | def drop_sequence_sql(name) 56 | "DROP SEQUENCE #{name}" 57 | end 58 | 59 | def change_sequence_sql(name, options = {}) 60 | return "" if options.blank? 61 | options.delete(:start) 62 | "ALTER SEQUENCE #{name}#{sequence_options_sql(options)}" 63 | end 64 | 65 | def sequence_options_sql(options = {}) 66 | sql = "" 67 | sql << increment_option_sql(options) if options[:increment] or options[:increment_by] 68 | sql << min_option_sql(options) 69 | sql << max_option_sql(options) 70 | sql << start_option_sql(options) if options[:start] or options[:start_with] 71 | sql << restart_option_sql(options) if options[:restart] or options[:restart_with] 72 | sql << cache_option_sql(options) if options[:cache] 73 | sql << cycle_option_sql(options) 74 | sql 75 | end 76 | 77 | def sequences 78 | # sequence_temp=# select * from temp; 79 | # -[ RECORD 1 ]-+-------------------- 80 | # sequence_name | temp 81 | # last_value | 7 82 | # start_value | 1 83 | # increment_by | 1 84 | # max_value | 9223372036854775807 85 | # min_value | 1 86 | # cache_value | 1 87 | # log_cnt | 26 88 | # is_cycled | f 89 | # is_called | t 90 | sequence_names = select_all("SELECT c.relname FROM pg_class c WHERE c.relkind = 'S' order by c.relname asc").map { |row| row['relname'] } 91 | 92 | all_sequences = [] 93 | 94 | sequence_names.each do |sequence_name| 95 | row = select_one("SELECT * FROM #{sequence_name}") 96 | 97 | options = { 98 | :increment => row['increment_by'].to_i, 99 | :min => row['min_value'].to_i, 100 | :max => row['max_value'].to_i, 101 | :start => row['start_value'].to_i, 102 | :cache => row['cache_value'].to_i, 103 | :cycle => row['is_cycled'] == 't' 104 | } 105 | 106 | all_sequences << SequenceDefinition.new(sequence_name, options) 107 | end 108 | 109 | all_sequences 110 | end 111 | 112 | protected 113 | def increment_option_sql(options = {}) 114 | " INCREMENT BY #{options[:increment] || options[:increment_by]}" 115 | end 116 | 117 | def min_option_sql(options = {}) 118 | case options[:min] 119 | when nil then "" 120 | when false then " NO MINVALUE" 121 | else " MINVALUE #{options[:min]}" 122 | end 123 | end 124 | 125 | def max_option_sql(options = {}) 126 | case options[:max] 127 | when nil then "" 128 | when false then " NO MAXVALUE" 129 | else " MAXVALUE #{options[:max]}" 130 | end 131 | end 132 | 133 | def restart_option_sql(options = {}) 134 | " RESTART WITH #{options[:restart] || options[:restart_with]}" 135 | end 136 | 137 | def start_option_sql(options = {}) 138 | " START WITH #{options[:start] || options[:start_with]}" 139 | end 140 | 141 | def cache_option_sql(options = {}) 142 | " CACHE #{options[:cache]}" 143 | end 144 | 145 | def cycle_option_sql(options = {}) 146 | case options[:cycle] 147 | when nil then "" 148 | when false then " NO CYCLE" 149 | else " CYCLE" 150 | end 151 | end 152 | 153 | end 154 | end 155 | end 156 | 157 | # todo: add JDBCAdapter? 158 | [:PostgreSQLAdapter].each do |adapter| 159 | begin 160 | ActiveRecord::ConnectionAdapters.const_get(adapter).class_eval do 161 | include PgSequencer::ConnectionAdapters::PostgreSQLAdapter 162 | end 163 | rescue 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /lib/pg_sequencer/railtie.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Code42, Inc. 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | module PgSequencer 21 | class Railtie < Rails::Railtie 22 | initializer "pg_sequencer.load_adapter" do 23 | ActiveSupport.on_load :active_record do 24 | require 'pg_sequencer/connection_adapters/postgresql_adapter' 25 | 26 | ActiveRecord::ConnectionAdapters.module_eval do 27 | include PgSequencer::ConnectionAdapters::PostgreSQLAdapter 28 | end 29 | 30 | ActiveRecord::SchemaDumper.class_eval do 31 | include PgSequencer::SchemaDumper 32 | end 33 | 34 | end 35 | 36 | # if defined?(ActiveRecord::Migration::CommandRecorder) 37 | # ActiveRecord::Migration::CommandRecorder.class_eval do 38 | # include PgSequencer::Migration::CommandRecorder 39 | # end 40 | # end 41 | # 42 | # # PgSequencer::Adapter.load! 43 | # end 44 | 45 | end # initializer 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/pg_sequencer/schema_dumper.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Code42, Inc. 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | module PgSequencer 21 | module SchemaDumper 22 | extend ActiveSupport::Concern 23 | 24 | included do 25 | alias_method_chain :tables, :sequences 26 | end 27 | 28 | def tables_with_sequences(stream) 29 | tables_without_sequences(stream) 30 | sequences(stream) 31 | end 32 | 33 | private 34 | def sequences(stream) 35 | sequence_statements = @connection.sequences.map do |sequence| 36 | statement_parts = [ ('create_sequence ') + sequence.name.inspect ] 37 | statement_parts << (':increment => ' + sequence.options[:increment].inspect) 38 | statement_parts << (':min => ' + sequence.options[:min].inspect) 39 | statement_parts << (':max => ' + sequence.options[:max].inspect) 40 | statement_parts << (':start => ' + sequence.options[:start].inspect) 41 | statement_parts << (':cache => ' + sequence.options[:cache].inspect) 42 | statement_parts << (':cycle => ' + sequence.options[:cycle].inspect) 43 | 44 | ' ' + statement_parts.join(', ') 45 | end 46 | 47 | stream.puts sequence_statements.sort.join("\n") 48 | stream.puts 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/pg_sequencer/version.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Code42, Inc. 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | module PgSequencer 21 | VERSION = "0.0.2" 22 | end 23 | -------------------------------------------------------------------------------- /pg_sequencer.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | 3 | # Maintain your gem's version: 4 | require "pg_sequencer/version" 5 | 6 | # Describe your gem and declare its dependencies: 7 | Gem::Specification.new do |s| 8 | s.name = "pg_sequencer" 9 | s.version = PgSequencer::VERSION 10 | s.authors = ["Tony Collen"] 11 | s.email = ["tonyc@code42.com"] 12 | s.homepage = "https://github.com/code42/pg_sequencer/" 13 | s.summary = "Manage postgres sequences in Rails migrations" 14 | s.description = "Sequences need some love. pg_sequencer teaches Rails what sequences are, and will dump them to schema.rb, and also lets you create/drop sequences in migrations." 15 | 16 | s.files = Dir["{app,config,db,lib}/**/*"] + ["Rakefile", "README.rdoc"] 17 | s.test_files = Dir["test/**/*"] 18 | 19 | # s.add_dependency "rails", "~> 3.1.0" 20 | s.add_dependency 'activerecord', '>= 3.0.0' 21 | s.add_development_dependency 'activerecord', '>= 3.1.0' 22 | s.add_development_dependency "pg", "0.11.0" 23 | s.add_development_dependency "guard" 24 | s.add_development_dependency "guard-test" 25 | s.add_development_dependency "shoulda-context" 26 | end 27 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Code42, Inc. 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | require 'rubygems' 21 | require 'test/unit' 22 | require 'active_record' 23 | require 'shoulda-context' 24 | require File.expand_path('../../lib/pg_sequencer', __FILE__) 25 | -------------------------------------------------------------------------------- /test/unit/postgresql_adapter_test.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Code42, Inc. 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | require 'helper' 21 | require 'pg_sequencer/connection_adapters/postgresql_adapter' 22 | 23 | class PostgreSQLAdapterTest < ActiveSupport::TestCase 24 | include PgSequencer::ConnectionAdapters::PostgreSQLAdapter 25 | 26 | setup do 27 | @options = { 28 | :increment => 1, 29 | :min => 1, 30 | :max => 2_000_000, 31 | :cache => 5, 32 | :cycle => true 33 | } 34 | end 35 | 36 | context "generating sequence option SQL" do 37 | context "for :increment" do 38 | should "include 'INCREMENT BY' in the SQL" do 39 | assert_equal(" INCREMENT BY 1", sequence_options_sql(:increment => 1)) 40 | assert_equal(" INCREMENT BY 2", sequence_options_sql(:increment => 2)) 41 | end 42 | 43 | should "not include the option if nil value specified" do 44 | assert_equal("", sequence_options_sql(:increment => nil)) 45 | end 46 | end 47 | 48 | context "for :min" do 49 | should "include 'MINVALUE' in the SQL if specified" do 50 | assert_equal(" MINVALUE 1", sequence_options_sql(:min => 1)) 51 | assert_equal(" MINVALUE 2", sequence_options_sql(:min => 2)) 52 | end 53 | 54 | should "not include 'MINVALUE' in SQL if set to nil" do 55 | assert_equal("", sequence_options_sql(:min => nil)) 56 | end 57 | 58 | should "set 'NO MINVALUE' if :min specified as false" do 59 | assert_equal(" NO MINVALUE", sequence_options_sql(:min => false)) 60 | end 61 | end 62 | 63 | context "for :max" do 64 | should "include 'MAXVALUE' in the SQL if specified" do 65 | assert_equal(" MAXVALUE 1", sequence_options_sql(:max => 1)) 66 | assert_equal(" MAXVALUE 2", sequence_options_sql(:max => 2)) 67 | end 68 | 69 | should "not include 'MAXVALUE' in SQL if set to nil" do 70 | assert_equal("", sequence_options_sql(:max => nil)) 71 | end 72 | 73 | should "set 'NO MAXVALUE' if :min specified as false" do 74 | assert_equal(" NO MAXVALUE", sequence_options_sql(:max => false)) 75 | end 76 | end 77 | 78 | context "for :start" do 79 | should "include 'START WITH' in SQL if specified" do 80 | assert_equal(" START WITH 1", sequence_options_sql(:start => 1)) 81 | assert_equal(" START WITH 2", sequence_options_sql(:start => 2)) 82 | assert_equal(" START WITH 500", sequence_options_sql(:start => 500)) 83 | end 84 | 85 | should "not include 'START WITH' in SQL if specified as nil" do 86 | assert_equal("", sequence_options_sql(:start => nil)) 87 | end 88 | end 89 | 90 | context "for :cache" do 91 | should "include 'CACHE' in SQL if specified" do 92 | assert_equal(" CACHE 1", sequence_options_sql(:cache => 1)) 93 | assert_equal(" CACHE 2", sequence_options_sql(:cache => 2)) 94 | assert_equal(" CACHE 500", sequence_options_sql(:cache => 500)) 95 | end 96 | end 97 | 98 | context "for :cycle" do 99 | should "include 'CYCLE' option if specified" do 100 | assert_equal(" CYCLE", sequence_options_sql(:cycle => true)) 101 | end 102 | 103 | should "include 'NO CYCLE' option if set as false" do 104 | assert_equal(" NO CYCLE", sequence_options_sql(:cycle => false)) 105 | end 106 | 107 | should "not include 'CYCLE' statement if specified as nil" do 108 | assert_equal("", sequence_options_sql(:cycle => nil)) 109 | end 110 | end 111 | 112 | should "include all options" do 113 | assert_equal " INCREMENT BY 1 MINVALUE 1 MAXVALUE 2000000 START WITH 1 CACHE 5 CYCLE", sequence_options_sql(@options.merge(:start => 1)) 114 | end 115 | end # generating sequence option SQL 116 | 117 | context "creating sequences" do 118 | context "without options" do 119 | should "generate the proper SQL" do 120 | assert_equal("CREATE SEQUENCE things", create_sequence_sql('things')) 121 | assert_equal("CREATE SEQUENCE blahs", create_sequence_sql('blahs')) 122 | end 123 | end 124 | 125 | context "with options" do 126 | should "include options at the end" do 127 | assert_equal("CREATE SEQUENCE things INCREMENT BY 1 MINVALUE 1 MAXVALUE 2000000 START WITH 1 CACHE 5 CYCLE", create_sequence_sql('things', @options.merge(:start => 1))) 128 | end 129 | end 130 | end # creating sequences 131 | 132 | context "altering sequences" do 133 | context "without options" do 134 | should "return a blank SQL statement" do 135 | assert_equal("", change_sequence_sql('things')) 136 | assert_equal("", change_sequence_sql('things', {})) 137 | assert_equal("", change_sequence_sql('things', nil)) 138 | end 139 | end 140 | 141 | context "with options" do 142 | 143 | should "include options at the end" do 144 | assert_equal("ALTER SEQUENCE things INCREMENT BY 1 MINVALUE 1 MAXVALUE 2000000 RESTART WITH 1 CACHE 5 CYCLE", change_sequence_sql('things', @options.merge(:restart => 1))) 145 | end 146 | end 147 | 148 | end # altering sequences 149 | 150 | context "dropping sequences" do 151 | should "generate the proper SQL" do 152 | assert_equal("DROP SEQUENCE seq_users", drop_sequence_sql('seq_users')) 153 | assert_equal("DROP SEQUENCE seq_items", drop_sequence_sql('seq_items')) 154 | end 155 | end # dropping sequences 156 | 157 | end 158 | -------------------------------------------------------------------------------- /test/unit/schema_dumper_test.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Code42, Inc. 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | require 'helper' 21 | 22 | class SchemaDumperTest < ActiveSupport::TestCase 23 | 24 | class SequenceDefinition < Struct.new(:name, :options); end 25 | 26 | class MockConnection 27 | attr_accessor :sequences 28 | 29 | def initialize(sequences = []) 30 | @sequences = sequences 31 | end 32 | 33 | end 34 | 35 | class MockStream 36 | attr_accessor :output 37 | def initialize; @output = []; end 38 | def puts(str = ""); @output << str; end 39 | def to_s; @output.join("\n"); end 40 | end 41 | 42 | class MockSchemaDumper 43 | def initialize(connection) 44 | @connection = connection 45 | end 46 | 47 | def self.dump(conn, stream) 48 | new(conn).dump(stream) 49 | end 50 | 51 | def header(stream) 52 | stream.puts '# Fake Schema Header' 53 | end 54 | 55 | def tables(stream) 56 | stream.puts '# (No Tables)' 57 | end 58 | 59 | def dump(stream) 60 | header(stream) 61 | tables(stream) 62 | trailer(stream) 63 | stream 64 | end 65 | 66 | def trailer(stream) 67 | stream.puts '# Fake Schema Trailer' 68 | end 69 | 70 | include PgSequencer::SchemaDumper 71 | end 72 | 73 | context "dumping the schema" do 74 | setup do 75 | @options = { 76 | :increment => 1, 77 | :min => 1, 78 | :max => 2_000_000, 79 | :start => 1, 80 | :cache => 5, 81 | :cycle => true 82 | } 83 | 84 | @stream = MockStream.new 85 | end 86 | 87 | should "output all sequences correctly" do 88 | sequences = ['seq_t_user', 'seq_t_item'].map do |name| 89 | SequenceDefinition.new(name, @options) 90 | end 91 | 92 | @conn = MockConnection.new(sequences) 93 | 94 | expected_output = <<-SCHEMAEND 95 | # Fake Schema Header 96 | # (No Tables) 97 | create_sequence "seq_t_item", :increment => 1, :min => 1, :max => 2000000, :start => 1, :cache => 5, :cycle => true 98 | create_sequence "seq_t_user", :increment => 1, :min => 1, :max => 2000000, :start => 1, :cache => 5, :cycle => true 99 | 100 | # Fake Schema Trailer 101 | SCHEMAEND 102 | 103 | MockSchemaDumper.dump(@conn, @stream) 104 | assert_equal(expected_output.strip, @stream.to_s) 105 | end 106 | 107 | context "when min specified as false" do 108 | setup do 109 | sequences = ['seq_t_user', 'seq_t_item'].map do |name| 110 | SequenceDefinition.new(name, @options.merge(:min => false)) 111 | end 112 | @conn = MockConnection.new(sequences) 113 | end 114 | 115 | should "properly quote false values in schema output" do 116 | expected_output = <<-SCHEMAEND 117 | # Fake Schema Header 118 | # (No Tables) 119 | create_sequence "seq_t_item", :increment => 1, :min => false, :max => 2000000, :start => 1, :cache => 5, :cycle => true 120 | create_sequence "seq_t_user", :increment => 1, :min => false, :max => 2000000, :start => 1, :cache => 5, :cycle => true 121 | 122 | # Fake Schema Trailer 123 | SCHEMAEND 124 | 125 | MockSchemaDumper.dump(@conn, @stream) 126 | assert_equal(expected_output.strip, @stream.to_s) 127 | end 128 | end 129 | end 130 | end 131 | --------------------------------------------------------------------------------