module Sequel class Schema COMMA_SEPARATOR = ', '.freeze COLUMN_DEF = '%s %s'.freeze UNIQUE = ' UNIQUE'.freeze NOT_NULL = ' NOT NULL'.freeze DEFAULT = ' DEFAULT %s'.freeze PRIMARY_KEY = ' PRIMARY KEY'.freeze REFERENCES = ' REFERENCES %s'.freeze ON_DELETE = ' ON DELETE %s'.freeze AUTOINCREMENT = ' AUTOINCREMENT'.freeze RESTRICT = 'RESTRICT'.freeze CASCADE = 'CASCADE'.freeze NO_ACTION = 'NO ACTION'.freeze SET_NULL = 'SET NULL'.freeze SET_DEFAULT = 'SET DEFAULT'.freeze TYPES = Hash.new {|h, k| k} TYPES[:double] = 'double precision' def self.on_delete_action(action) case action when :restrict: RESTRICT when :cascade: CASCADE when :set_null: SET_NULL when :set_default: SET_DEFAULT else NO_ACTION end end def self.column_definition(column) c = COLUMN_DEF % [column[:name], TYPES[column[:type]]] c << UNIQUE if column[:unique] c << NOT_NULL if column[:null] == false c << DEFAULT % PGconn.quote(column[:default]) if column.include?(:default) c << PRIMARY_KEY if column[:primary_key] c << REFERENCES % column[:table] if column[:table] c << ON_DELETE % on_delete_action(column[:on_delete]) if column[:on_delete] c << AUTOINCREMENT if column[:auto_increment] c end def self.create_table_column_list(columns) columns.map {|c| column_definition(c)}.join(COMMA_SEPARATOR) end CREATE_INDEX = 'CREATE INDEX %s ON %s (%s);'.freeze CREATE_UNIQUE_INDEX = 'CREATE UNIQUE INDEX %s ON %s (%s);'.freeze INDEX_NAME = '%s_%s_index'.freeze UNDERSCORE = '_'.freeze def self.index_definition(table_name, index) fields = index[:columns].join(COMMA_SEPARATOR) index_name = index[:name] || INDEX_NAME % [table_name, index[:columns].join(UNDERSCORE)] (index[:unique] ? CREATE_UNIQUE_INDEX : CREATE_INDEX) % [index_name, table_name, fields] end def self.create_indexes_sql(table_name, indexes) indexes.map {|i| index_definition(table_name, i)}.join end CREATE_TABLE = "CREATE TABLE %s (%s);".freeze def self.create_table_sql(name, columns, indexes = nil) sql = CREATE_TABLE % [name, create_table_column_list(columns)] sql << create_indexes_sql(name, indexes) if indexes && !indexes.empty? sql end DROP_TABLE = "DROP TABLE %s CASCADE;".freeze def self.drop_table_sql(name) DROP_TABLE % name end class Generator attr_reader :table_name def initialize(table_name, auto_primary_key = nil, &block) @table_name = table_name @primary_key = auto_primary_key @columns = [] @indexes = [] instance_eval(&block) end def method_missing(type, name = nil, opts = nil) return super unless name column(name, type, opts) end def primary_key(name, type = nil, opts = nil) @primary_key = { :name => name, :type => type || :serial, :primary_key => true }.merge(opts || {}) end def primary_key_name @primary_key && @primary_key[:name] end def column(name, type, opts = nil) @columns << {:name => name, :type => type}.merge(opts || {}) end def foreign_key(name, opts) @columns << {:name => name, :type => :integer}.merge(opts || {}) end def has_column?(name) @columns.each {|c| return true if c[:name] == name} false end def index(columns, opts = nil) columns = [columns] unless columns.is_a?(Array) @indexes << {:columns => columns}.merge(opts || {}) end def create_sql if @primary_key && !has_column?(@primary_key[:name]) @columns.unshift(@primary_key) end Schema.create_table_sql(@table_name, @columns, @indexes) end def drop_sql Schema.drop_table_sql(@table_name) end end attr_reader :instructions def initialize(&block) @instructions = [] instance_eval(&block) if block end def auto_primary_key(name, type = nil, opts = nil) @auto_primary_key = { :name => name, :type => type || :serial, :primary_key => true }.merge(opts || {}) end def create_table(table_name, &block) @instructions << Generator.new(table_name, @auto_primary_key, &block) end def create(db) @instructions.each do |s| db.execute(s.create_sql) end end def drop(db) @instructions.reverse_each do |s| db.execute(s.drop_sql) if db.table_exists?(s.table_name) end end def recreate(db) drop(db) create(db) end end end