├── .gitignore ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── lib └── sequel │ ├── extensions │ └── postgres_schemata.rb │ └── postgres │ ├── schemata.rb │ └── schemata │ └── version.rb ├── sequel-postgres-schemata.gemspec └── spec ├── schemata_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in sequel-postgres-schemata.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Conjur Inc. 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sequel::Postgres::Schemata 2 | 3 | Easily manipulate Postgres schemas in Sequel. 4 | 5 | ## Installation 6 | 7 | Add this line to your application's Gemfile: 8 | 9 | gem 'sequel-postgres-schemata' 10 | 11 | And then execute: 12 | 13 | $ bundle 14 | 15 | Or install it yourself as: 16 | 17 | $ gem install sequel-postgres-schemata 18 | 19 | ## Usage 20 | ```ruby 21 | Sequel.extension :postgres_schemata 22 | 23 | db = Sequel.connect adapter: 'postgres', search_path: %w(foo public) 24 | db.create_schema :bar 25 | 26 | db.search_path # => [:foo, :public] 27 | 28 | db.search_path :baz do 29 | db.search_path # => [:baz] 30 | end 31 | 32 | db.search_path :baz, prepend: true do 33 | db.search_path # => [:baz, :foo, :public] 34 | end 35 | 36 | db.schemata # => [:pg_toast, :pg_temp_1, :pg_toast_temp_1, :pg_catalog, :public, :information_schema, :bar] 37 | db.current_schemata # => [:public] 38 | db.search_path = [:bar, :foo, :public] 39 | db.current_schemata # => [:bar, :public] 40 | db.rename_schema :bar, :foo 41 | db.current_schemata # => [:foo, :public] 42 | 43 | ``` 44 | 45 | ## Contributing 46 | 47 | 1. Fork it 48 | 2. Create your feature branch (`git checkout -b my-new-feature`) 49 | 3. Commit your changes (`git commit -am 'Add some feature'`) 50 | 4. Push to the branch (`git push origin my-new-feature`) 51 | 5. Create new Pull Request 52 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task :default => :spec 7 | -------------------------------------------------------------------------------- /lib/sequel/extensions/postgres_schemata.rb: -------------------------------------------------------------------------------- 1 | require 'sequel/postgres/schemata' 2 | -------------------------------------------------------------------------------- /lib/sequel/postgres/schemata.rb: -------------------------------------------------------------------------------- 1 | require "sequel/postgres/schemata/version" 2 | 3 | Sequel.extension :pg_array 4 | 5 | module Sequel 6 | module Postgres 7 | module Schemata 8 | module DatabaseMethods 9 | 10 | # List all existing schematas (including the system ones). 11 | # Returns a symbol list. 12 | def schemata 13 | metadata_dataset.select(:nspname).from(:pg_namespace).map(:nspname).map(&:to_sym) 14 | end 15 | 16 | # Returns a symbol list containing the current search path. 17 | # Note that the search path can contain non-existent schematas. 18 | # If given a block and an argument, instead temporarily changes 19 | # the search path inside the block. It also accepts several arguments, 20 | # in which case it treats them as an array of schemata to put in search path. 21 | # If you use prepend: true, it prepends any given schemata to the current search path. 22 | def search_path *a, prepend: false, &block 23 | if block_given? 24 | a = a.flatten 25 | a += search_path if prepend 26 | run_with_search_path a, &block 27 | else 28 | get_search_path 29 | end 30 | end 31 | 32 | # Sets the search path. Starting with Postgres 9.2 it can contain 33 | # non-existent schematas. 34 | # Accepted formats include a single symbol, a single string (split on ,) 35 | # and lists of symbols or strings. 36 | def search_path= search_path 37 | case search_path 38 | when String 39 | search_path = search_path.split(",").map{|s| s.strip} 40 | when Symbol 41 | search_path = [search_path] 42 | when Array 43 | # nil 44 | else 45 | raise Error, "unrecognized value for search_path: #{search_path.inspect}" 46 | end 47 | self << "SET search_path = #{search_path.map{|s| "\"#{s.to_s.gsub('"', '""')}\""}.join(',')}" 48 | end 49 | 50 | # Returns the current schemata, as returned by current_schemas(false). 51 | def current_schemata 52 | extension :pg_array 53 | metadata_dataset.select(Sequel::function(:current_schemas, false). 54 | cast('varchar[]')).single_value.map(&:to_sym) 55 | end 56 | 57 | # Renames a schema 58 | def rename_schema from, to 59 | self << RENAME_SCHEMA_SQL % [from.to_s.gsub('"', '""'), to.to_s.gsub('"', '""')] 60 | end 61 | 62 | private 63 | 64 | def get_search_path 65 | metadata_dataset.with_sql(SHOW_SEARCH_PATH). 66 | single_value.scan(SCHEMA_SCAN_RE).flatten. 67 | map{|s|s.strip.sub(SCHEMA_SUB_RE, '\1').gsub('""', '"').to_sym} 68 | end 69 | 70 | def run_with_search_path path, &block 71 | old_path = search_path 72 | 73 | begin 74 | self.search_path = path 75 | yield 76 | ensure 77 | self.search_path = old_path 78 | end 79 | end 80 | 81 | SHOW_SEARCH_PATH = "SHOW search_path".freeze 82 | SCHEMA_SCAN_RE = /(?<=\A|,)(".*?"|.*?)(?=,|\z)/.freeze 83 | SCHEMA_SUB_RE = /\A"(.*)"\z/.freeze 84 | RENAME_SCHEMA_SQL = 'ALTER SCHEMA "%s" RENAME TO "%s"'.freeze 85 | end 86 | end 87 | 88 | module DatabaseMethods 89 | include ::Sequel::Postgres::Schemata::DatabaseMethods 90 | end 91 | 92 | Database.send :include, ::Sequel::Postgres::Schemata::DatabaseMethods if defined? Database 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /lib/sequel/postgres/schemata/version.rb: -------------------------------------------------------------------------------- 1 | module Sequel 2 | module Postgres 3 | module Schemata 4 | VERSION = "0.1.3" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /sequel-postgres-schemata.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'sequel/postgres/schemata/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "sequel-postgres-schemata" 8 | spec.version = Sequel::Postgres::Schemata::VERSION 9 | spec.authors = ["Rafał Rzepecki"] 10 | spec.email = ["rafal@conjur.net"] 11 | spec.description = %q{Allows easy manipulation of Postgres schemas from Ruby with Sequel} 12 | spec.summary = %q{Postgres schema manipulation} 13 | spec.homepage = "https://github.com/dividedmind/sequel-postgres-schemata" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files`.split($/) 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_runtime_dependency "sequel", ">= 4.3", "< 6" 22 | 23 | spec.add_development_dependency "bundler", ">= 1.3", "< 3" 24 | spec.add_development_dependency "rake", "~> 12.3" 25 | spec.add_development_dependency "rspec", "~> 2.14" 26 | spec.add_development_dependency "pg", "~> 0.16" 27 | end 28 | -------------------------------------------------------------------------------- /spec/schemata_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Sequel::Postgres::Schemata do 4 | 5 | let(:db) { Sequel::connect adapter: 'postgres', search_path: %w(foo public) } 6 | let(:plain_db) { Sequel::connect adapter: 'postgres' } 7 | 8 | describe "#schemata" do 9 | it "lists all existing schematas" do 10 | schemata = db.schemata 11 | expect(schemata).to include(:public) 12 | expect(schemata).not_to include(:foo) 13 | end 14 | end 15 | 16 | describe "#search_path" do 17 | it "returns the search path" do 18 | expect(db.search_path).to eq(%i(foo public)) 19 | end 20 | 21 | it "correctly handles the default list" do 22 | expect(plain_db.search_path).to eq(%i($user public)) 23 | end 24 | 25 | describe "with a block" do 26 | it "changes the search path temporarily" do 27 | db.search_path :bar do 28 | expect(db.search_path).to eq(%i(bar)) 29 | end 30 | expect(db.search_path).to eq(%i(foo public)) 31 | end 32 | 33 | it "resets the search path when the given block raises an error" do 34 | class MyContrivedError < StandardError; end 35 | 36 | begin 37 | db.search_path :bar do 38 | expect(db.search_path).to eq(%i(bar)) 39 | raise MyContrivedError.new 40 | end 41 | rescue MyContrivedError 42 | # Gobble. 43 | end 44 | expect(db.search_path).to eq(%i(foo public)) 45 | end 46 | 47 | it "accepts symbols as arglist" do 48 | db.search_path :bar, :baz do 49 | expect(db.search_path).to eq(%i(bar baz)) 50 | end 51 | expect(db.search_path).to eq(%i(foo public)) 52 | end 53 | 54 | it "allows prepending with prepend: true" do 55 | db.search_path :bar, prepend: true do 56 | expect(db.search_path).to eq(%i(bar foo public)) 57 | end 58 | expect(db.search_path).to eq(%i(foo public)) 59 | end 60 | end 61 | end 62 | 63 | describe "#search_path=" do 64 | it "accepts a single symbol" do 65 | db.search_path = :bar 66 | expect(db.search_path).to eq(%i(bar)) 67 | end 68 | 69 | it "accepts a single string" do 70 | db.search_path = 'bar' 71 | expect(db.search_path).to eq(%i(bar)) 72 | end 73 | 74 | it "accepts a formatted string" do 75 | db.search_path = 'bar, baz' 76 | expect(db.search_path).to eq(%i(bar baz)) 77 | end 78 | 79 | it "accepts a symbol list" do 80 | db.search_path = %i(bar baz) 81 | expect(db.search_path).to eq(%i(bar baz)) 82 | end 83 | 84 | it "accepts a string list" do 85 | db.search_path = %w(bar baz) 86 | expect(db.search_path).to eq(%i(bar baz)) 87 | end 88 | 89 | it "quotes the string list correctly" do 90 | db.search_path = ["bar\" ',", "baz"] 91 | expect(db.search_path).to eq([:"bar\" ',", :baz]) 92 | end 93 | end 94 | 95 | describe "#current_schemata" do 96 | it "returns the current schemata" do 97 | expect(db.current_schemata).to eq(%i(public)) 98 | end 99 | end 100 | 101 | describe "#rename_schema" do 102 | it "renames a schema" do 103 | db.transaction rollback: :always do 104 | db.create_schema :test_schema 105 | expect(db.schemata).to include(:test_schema) 106 | expect(db.current_schemata).to eq(%i(public)) 107 | db.rename_schema :test_schema, :foo 108 | expect(db.current_schemata).to eq(%i(foo public)) 109 | end 110 | end 111 | end 112 | 113 | end 114 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'sequel' 2 | 3 | Sequel.extension :postgres_schemata 4 | --------------------------------------------------------------------------------