mimi-db 0.2.7 → 0.3.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.
- checksums.yaml +4 -4
- data/examples/my_app.rb +3 -12
- data/examples/my_app_2.rb +20 -0
- data/examples/my_model.rb +13 -0
- data/lib/mimi/db/dictate/dsl.rb +2 -2
- data/lib/mimi/db/dictate/explorer.rb +34 -20
- data/lib/mimi/db/dictate/migrator.rb +35 -21
- data/lib/mimi/db/dictate/schema_definition.rb +61 -28
- data/lib/mimi/db/dictate/schema_diff.rb +1 -6
- data/lib/mimi/db/dictate/type_defaults.rb +167 -0
- data/lib/mimi/db/dictate.rb +15 -53
- data/lib/mimi/db/extensions/sequel-cockroachdb.rb +39 -0
- data/lib/mimi/db/extensions/sequel-database.rb +23 -0
- data/lib/mimi/db/extensions/sequel-postgres.rb +3 -0
- data/lib/mimi/db/extensions/sequel-sqlite.rb +3 -0
- data/lib/mimi/db/extensions.rb +14 -10
- data/lib/mimi/db/foreign_key.rb +9 -7
- data/lib/mimi/db/helpers.rb +39 -41
- data/lib/mimi/db/lock.rb +4 -0
- data/lib/mimi/db/model.rb +64 -0
- data/lib/mimi/db/version.rb +1 -1
- data/lib/mimi/db.rb +50 -23
- data/lib/tasks/db.rake +9 -1
- data/mimi-db.gemspec +1 -1
- metadata +13 -5
    
        data/lib/mimi/db/dictate.rb
    CHANGED
    
    | @@ -3,65 +3,26 @@ require_relative 'dictate/schema_definition' | |
| 3 3 | 
             
            require_relative 'dictate/schema_diff'
         | 
| 4 4 | 
             
            require_relative 'dictate/explorer'
         | 
| 5 5 | 
             
            require_relative 'dictate/migrator'
         | 
| 6 | 
            +
            require_relative 'dictate/type_defaults'
         | 
| 6 7 |  | 
| 7 8 | 
             
            module Mimi
         | 
| 8 9 | 
             
              module DB
         | 
| 9 10 | 
             
                module Dictate
         | 
| 10 | 
            -
                  TYPE_DEFAULTS = {
         | 
| 11 | 
            -
                    # sqlite3: {
         | 
| 12 | 
            -
                    #   string: { name: 'varchar', limit: 32 }
         | 
| 13 | 
            -
                    # }
         | 
| 14 | 
            -
                  }.freeze
         | 
| 15 | 
            -
             | 
| 16 11 | 
             
                  def self.included(base)
         | 
| 17 12 | 
             
                    base.extend Mimi::DB::Dictate::DSL
         | 
| 18 13 | 
             
                  end
         | 
| 19 14 |  | 
| 20 15 | 
             
                  def self.start
         | 
| 21 | 
            -
                    ActiveRecord::Base.extend Mimi::DB::Dictate::DSL
         | 
| 22 16 | 
             
                  end
         | 
| 23 17 |  | 
| 18 | 
            +
                  # Returns all registered schema definitions
         | 
| 19 | 
            +
                  #
         | 
| 20 | 
            +
                  # @return [Hash] a map of <table_name> -> <schema_definition>
         | 
| 21 | 
            +
                  #
         | 
| 24 22 | 
             
                  def self.schema_definitions
         | 
| 25 23 | 
             
                    @schema_definitions ||= {}
         | 
| 26 24 | 
             
                  end
         | 
| 27 25 |  | 
| 28 | 
            -
                  def self.adapter_type
         | 
| 29 | 
            -
                    ca = ActiveRecord::ConnectionAdapters
         | 
| 30 | 
            -
                    c  = ActiveRecord::Base.connection
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                    # TODO: postgres???
         | 
| 33 | 
            -
                    return :cockroachdb if ca.const_defined?(:CockroachDBAdapter) && c.is_a?(ca::PostgreSQLAdapter)
         | 
| 34 | 
            -
             | 
| 35 | 
            -
                    return :postgresql if ca.const_defined?(:PostgreSQLAdapter) && c.is_a?(ca::PostgreSQLAdapter)
         | 
| 36 | 
            -
             | 
| 37 | 
            -
                    return :mysql if ca.const_defined?(:AbstractMysqlAdapter) && c.is_a?(ca::AbstractMysqlAdapter)
         | 
| 38 | 
            -
             | 
| 39 | 
            -
                    return :sqlite3 if ca.const_defined?(:SQLite3Adapter) && c.is_a?(ca::SQLite3Adapter)
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                    raise 'Unrecognized database adapter type'
         | 
| 42 | 
            -
                  end
         | 
| 43 | 
            -
             | 
| 44 | 
            -
                  # Returns type defaults based on given type:
         | 
| 45 | 
            -
                  #   :string
         | 
| 46 | 
            -
                  #   :text
         | 
| 47 | 
            -
                  #   :integer etc
         | 
| 48 | 
            -
                  #
         | 
| 49 | 
            -
                  def self.type_defaults(type)
         | 
| 50 | 
            -
                    type = type.to_sym
         | 
| 51 | 
            -
                    connection_defaults = ActiveRecord::Base.connection.native_database_types
         | 
| 52 | 
            -
                    adapter_defaults = TYPE_DEFAULTS[DB::Dictate.adapter_type]
         | 
| 53 | 
            -
                    d = (adapter_defaults && adapter_defaults[type]) || connection_defaults[type] || {}
         | 
| 54 | 
            -
                    d = {
         | 
| 55 | 
            -
                      sql_type: d.is_a?(String) ? d : d[:name],
         | 
| 56 | 
            -
                      limit: d.is_a?(String) ? nil : d[:limit]
         | 
| 57 | 
            -
                    }
         | 
| 58 | 
            -
                    if type == :primary_key
         | 
| 59 | 
            -
                      d[:primary_key] = true
         | 
| 60 | 
            -
                      d[:not_null] = true
         | 
| 61 | 
            -
                    end
         | 
| 62 | 
            -
                    d
         | 
| 63 | 
            -
                  end
         | 
| 64 | 
            -
             | 
| 65 26 | 
             
                  # Updates the DB schema to the target schema defined in models
         | 
| 66 27 | 
             
                  #
         | 
| 67 28 | 
             
                  # Default options from Migrator::DEFAULTS:
         | 
| @@ -71,18 +32,18 @@ module Mimi | |
| 71 32 | 
             
                  #       indexes: false
         | 
| 72 33 | 
             
                  #     },
         | 
| 73 34 | 
             
                  #     dry_run: false,
         | 
| 74 | 
            -
                  #     logger: nil # will use  | 
| 35 | 
            +
                  #     logger: nil # will use Mimi::DB.logger
         | 
| 75 36 | 
             
                  #
         | 
| 76 37 | 
             
                  # @param opts [Hash]
         | 
| 77 38 | 
             
                  #
         | 
| 78 39 | 
             
                  def self.update_schema!(opts = {})
         | 
| 79 | 
            -
                    logger = opts[:logger] ||  | 
| 80 | 
            -
                    logger. | 
| 40 | 
            +
                    logger = opts[:logger] || Mimi::DB.logger
         | 
| 41 | 
            +
                    logger.debug 'Mimi::DB::Dictate started updating DB schema'
         | 
| 81 42 | 
             
                    t_start = Time.now
         | 
| 82 43 | 
             
                    Mimi::DB.all_table_names.each { |t| Mimi::DB::Dictate::Migrator.new(t, opts).run! }
         | 
| 83 | 
            -
                    logger. | 
| 44 | 
            +
                    logger.debug 'Mimi::DB::Dictate finished updating DB schema (%.3fs)' % [Time.now - t_start]
         | 
| 84 45 | 
             
                  rescue StandardError => e
         | 
| 85 | 
            -
                    logger.error "DB::Dictate failed to update DB schema: #{e}"
         | 
| 46 | 
            +
                    logger.error "Mimi::DB::Dictate failed to update DB schema: #{e}"
         | 
| 86 47 | 
             
                    raise
         | 
| 87 48 | 
             
                  end
         | 
| 88 49 |  | 
| @@ -92,22 +53,23 @@ module Mimi | |
| 92 53 | 
             
                  # @return [Hash]
         | 
| 93 54 | 
             
                  #
         | 
| 94 55 | 
             
                  def self.diff_schema(opts = {})
         | 
| 95 | 
            -
                    logger = opts[:logger] ||  | 
| 96 | 
            -
                    diff = { add_tables: [], change_tables: [], drop_tables: []}
         | 
| 56 | 
            +
                    logger = opts[:logger] || Mimi::DB.logger
         | 
| 57 | 
            +
                    diff = { add_tables: [], change_tables: [], drop_tables: [] }
         | 
| 97 58 | 
             
                    Mimi::DB.all_table_names.each do |t|
         | 
| 98 59 | 
             
                      m = Mimi::DB::Dictate::Migrator.new(t, opts)
         | 
| 99 60 | 
             
                      if m.from_schema && m.to_schema.nil?
         | 
| 100 61 | 
             
                        diff[:drop_tables] << t
         | 
| 101 62 | 
             
                      elsif m.from_schema && m.to_schema
         | 
| 63 | 
            +
                        logger.debug "DB::Dictate comparing '#{t}'"
         | 
| 102 64 | 
             
                        t_diff = Mimi::DB::Dictate::SchemaDiff.diff(m.from_schema, m.to_schema)
         | 
| 103 65 | 
             
                        diff[:change_tables] << t_diff unless t_diff[:columns].empty? && t_diff[:indexes].empty?
         | 
| 104 | 
            -
                      elsif m.from_schema.nil? && | 
| 66 | 
            +
                      elsif m.from_schema.nil? && m.to_schema
         | 
| 105 67 | 
             
                        diff[:add_tables] << m.to_schema
         | 
| 106 68 | 
             
                      end
         | 
| 107 69 | 
             
                    end
         | 
| 108 70 | 
             
                    diff
         | 
| 109 71 | 
             
                  rescue StandardError => e
         | 
| 110 | 
            -
                    logger.error "DB::Dictate failed to  | 
| 72 | 
            +
                    logger.error "DB::Dictate failed to compare schema: #{e}"
         | 
| 111 73 | 
             
                    raise
         | 
| 112 74 | 
             
                  end
         | 
| 113 75 | 
             
                end # module Dictate
         | 
| @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'sequel/adapters/postgres'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Sequel
         | 
| 6 | 
            +
              #
         | 
| 7 | 
            +
              # A simplistic and not fully functional CockroachDB adapter
         | 
| 8 | 
            +
              #
         | 
| 9 | 
            +
              # TODO: to replace with a better alternative once available
         | 
| 10 | 
            +
              #
         | 
| 11 | 
            +
              module Cockroach
         | 
| 12 | 
            +
                class Database < Sequel::Postgres::Database
         | 
| 13 | 
            +
                  set_adapter_scheme :cockroach
         | 
| 14 | 
            +
                  set_adapter_scheme :cockroachdb
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  # Cockroach DB only supports one savepoint
         | 
| 17 | 
            +
                  def supports_savepoints?
         | 
| 18 | 
            +
                    false
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def server_version(*)
         | 
| 22 | 
            +
                    80000 # mimics Postgres v8
         | 
| 23 | 
            +
                    # 100000 # mimics Postgres v10
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  private
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def dataset_class_default
         | 
| 29 | 
            +
                    Dataset
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                class Dataset < Sequel::Postgres::Dataset
         | 
| 34 | 
            +
                  def default_timestamp_format
         | 
| 35 | 
            +
                    "'%Y-%m-%d %H:%M:%S%N%:z'"
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                end # class Dataset
         | 
| 38 | 
            +
              end # module Cockroach
         | 
| 39 | 
            +
            end # module Sequel
         | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'sequel'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Sequel
         | 
| 6 | 
            +
              class Database
         | 
| 7 | 
            +
                #
         | 
| 8 | 
            +
                # Fixed behaviour for Sequel's log_exception()
         | 
| 9 | 
            +
                #
         | 
| 10 | 
            +
                # Reason:
         | 
| 11 | 
            +
                #   * handled exceptions should not be logged as errors
         | 
| 12 | 
            +
                #   * unhandled exceptions will be logged at the application level
         | 
| 13 | 
            +
                #
         | 
| 14 | 
            +
                def log_exception(exception, message, *)
         | 
| 15 | 
            +
                  text_message = "#{self.class}(#{exception.class}): #{exception.message}"
         | 
| 16 | 
            +
                  logger_message = { m: text_message, sql: message }
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  # In case logger does not support structured data, implement a #to_s method
         | 
| 19 | 
            +
                  logger_message.define_singleton_method(:to_s) { text_message }
         | 
| 20 | 
            +
                  log_each(:debug, logger_message)
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end # class Database
         | 
| 23 | 
            +
            end # module Sequel
         | 
    
        data/lib/mimi/db/extensions.rb
    CHANGED
    
    | @@ -2,16 +2,20 @@ module Mimi | |
| 2 2 | 
             
              module DB
         | 
| 3 3 | 
             
                module Extensions
         | 
| 4 4 | 
             
                  def self.start
         | 
| 5 | 
            -
                     | 
| 6 | 
            -
                     | 
| 7 | 
            -
             | 
| 8 | 
            -
                     | 
| 9 | 
            -
             | 
| 10 | 
            -
                     | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 5 | 
            +
                    adapter_name = Mimi::DB.sequel_config[:adapter]
         | 
| 6 | 
            +
                    require_relative 'extensions/sequel-database'
         | 
| 7 | 
            +
                    case adapter_name
         | 
| 8 | 
            +
                    when 'sqlite'
         | 
| 9 | 
            +
                      require_relative 'extensions/sequel-sqlite'
         | 
| 10 | 
            +
                    when 'postgres'
         | 
| 11 | 
            +
                      require_relative 'extensions/sequel-postgres'
         | 
| 12 | 
            +
                    when 'cockroachdb'
         | 
| 13 | 
            +
                      require_relative 'extensions/sequel-postgres'
         | 
| 14 | 
            +
                      require_relative 'extensions/sequel-cockroachdb'
         | 
| 15 | 
            +
                    else
         | 
| 16 | 
            +
                      # load nothing
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
                    Sequel::Model.plugin :timestamps
         | 
| 15 19 | 
             
                  end
         | 
| 16 20 | 
             
                end # module Extensions
         | 
| 17 21 | 
             
              end # module DB
         | 
    
        data/lib/mimi/db/foreign_key.rb
    CHANGED
    
    | @@ -1,20 +1,22 @@ | |
| 1 1 | 
             
            module Mimi
         | 
| 2 2 | 
             
              module DB
         | 
| 3 3 | 
             
                module ForeignKey
         | 
| 4 | 
            -
                   | 
| 4 | 
            +
                  # TODO: refactor and re-implement
         | 
| 5 5 |  | 
| 6 | 
            -
                   | 
| 6 | 
            +
                  # extend ActiveSupport::Concern
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  # class_methods do
         | 
| 7 9 | 
             
                    #
         | 
| 8 10 | 
             
                    # Explicitly specify a (bigint) foreign key
         | 
| 9 11 | 
             
                    #
         | 
| 10 | 
            -
                    def foreign_key(name, opts = {})
         | 
| 11 | 
            -
             | 
| 12 | 
            -
                      raise 'Not implemented'
         | 
| 12 | 
            +
                    # def foreign_key(name, opts = {})
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                      # raise 'Not implemented'
         | 
| 13 15 |  | 
| 14 16 | 
             
                      # opts = { as: :integer, limit: 8 }.merge(opts)
         | 
| 15 17 | 
             
                      # field(name, opts)
         | 
| 16 18 | 
             
                      # index(name)
         | 
| 17 | 
            -
                    end
         | 
| 19 | 
            +
                    # end
         | 
| 18 20 |  | 
| 19 21 | 
             
                    # Redefines .belongs_to() with explicitly specified .foreign_key
         | 
| 20 22 | 
             
                    #
         | 
| @@ -25,7 +27,7 @@ module Mimi | |
| 25 27 | 
             
                    #   # orig_belongs_to(name, opts)
         | 
| 26 28 | 
             
                    #   super
         | 
| 27 29 | 
             
                    # end
         | 
| 28 | 
            -
                  end
         | 
| 30 | 
            +
                  # end
         | 
| 29 31 | 
             
                end # module ForeignKey
         | 
| 30 32 | 
             
              end # module DB
         | 
| 31 33 | 
             
            end # module Mimi
         | 
    
        data/lib/mimi/db/helpers.rb
    CHANGED
    
    | @@ -7,7 +7,7 @@ module Mimi | |
| 7 7 | 
             
                  # @return [Array<ActiveRecord::Base>]
         | 
| 8 8 | 
             
                  #
         | 
| 9 9 | 
             
                  def models
         | 
| 10 | 
            -
                     | 
| 10 | 
            +
                    Mimi::DB::Model.descendants
         | 
| 11 11 | 
             
                  end
         | 
| 12 12 |  | 
| 13 13 | 
             
                  # Returns a list of table names defined in models
         | 
| @@ -23,7 +23,7 @@ module Mimi | |
| 23 23 | 
             
                  # @return [Array<String>]
         | 
| 24 24 | 
             
                  #
         | 
| 25 25 | 
             
                  def db_table_names
         | 
| 26 | 
            -
                     | 
| 26 | 
            +
                    Mimi::DB.connection.tables
         | 
| 27 27 | 
             
                  end
         | 
| 28 28 |  | 
| 29 29 | 
             
                  # Returns a list of all discovered table names,
         | 
| @@ -56,6 +56,7 @@ module Mimi | |
| 56 56 | 
             
                  #   Mimi::DB.update_schema!(destructive: true)
         | 
| 57 57 | 
             
                  #
         | 
| 58 58 | 
             
                  def update_schema!(opts = {})
         | 
| 59 | 
            +
                    Mimi::DB.start
         | 
| 59 60 | 
             
                    opts[:logger] ||= Mimi::DB.logger
         | 
| 60 61 | 
             
                    Mimi::DB::Dictate.update_schema!(opts)
         | 
| 61 62 | 
             
                  end
         | 
| @@ -84,6 +85,7 @@ module Mimi | |
| 84 85 | 
             
                  # @return [Hash]
         | 
| 85 86 | 
             
                  #
         | 
| 86 87 | 
             
                  def diff_schema(opts = {})
         | 
| 88 | 
            +
                    Mimi::DB.start
         | 
| 87 89 | 
             
                    opts[:logger] ||= Mimi::DB.logger
         | 
| 88 90 | 
             
                    Mimi::DB::Dictate.diff_schema(opts)
         | 
| 89 91 | 
             
                  end
         | 
| @@ -91,29 +93,16 @@ module Mimi | |
| 91 93 | 
             
                  # Creates the database specified in the current configuration.
         | 
| 92 94 | 
             
                  #
         | 
| 93 95 | 
             
                  def create!
         | 
| 94 | 
            -
                     | 
| 95 | 
            -
                    db_database = Mimi::DB.active_record_config['database']
         | 
| 96 | 
            -
                    slim_url = "#{db_adapter}//<host>:<port>/#{db_database}"
         | 
| 97 | 
            -
                    Mimi::DB.logger.info "Mimi::DB.create! creating database: #{slim_url}"
         | 
| 98 | 
            -
                    original_stdout = $stdout
         | 
| 99 | 
            -
                    original_stderr = $stderr
         | 
| 100 | 
            -
                    $stdout = StringIO.new
         | 
| 101 | 
            -
                    $stderr = StringIO.new
         | 
| 102 | 
            -
                    ActiveRecord::Tasks::DatabaseTasks.root = Mimi.app_root_path
         | 
| 103 | 
            -
                    ActiveRecord::Tasks::DatabaseTasks.create(Mimi::DB.active_record_config)
         | 
| 104 | 
            -
                    Mimi::DB.logger.debug "Mimi::DB.create! out:#{$stdout.string}, err:#{$stderr.string}"
         | 
| 105 | 
            -
                  ensure
         | 
| 106 | 
            -
                    $stdout = original_stdout
         | 
| 107 | 
            -
                    $stderr = original_stderr
         | 
| 96 | 
            +
                    raise 'Not implemented'
         | 
| 108 97 | 
             
                  end
         | 
| 109 98 |  | 
| 110 99 | 
             
                  # Tries to establish connection, returns true if the database exist
         | 
| 111 100 | 
             
                  #
         | 
| 112 101 | 
             
                  def database_exist?
         | 
| 113 | 
            -
                     | 
| 114 | 
            -
                    ActiveRecord::Base.connection
         | 
| 102 | 
            +
                    Mimi::DB.connection.test_connection
         | 
| 115 103 | 
             
                    true
         | 
| 116 | 
            -
                  rescue  | 
| 104 | 
            +
                  rescue StandardError => e
         | 
| 105 | 
            +
                    Mimi::DB.logger.error "DB: database_exist? failed with: #{e}"
         | 
| 117 106 | 
             
                    false
         | 
| 118 107 | 
             
                  end
         | 
| 119 108 |  | 
| @@ -130,31 +119,17 @@ module Mimi | |
| 130 119 | 
             
                  # Drops the database specified in the current configuration.
         | 
| 131 120 | 
             
                  #
         | 
| 132 121 | 
             
                  def drop!
         | 
| 133 | 
            -
                     | 
| 134 | 
            -
                    original_stderr = $stderr
         | 
| 135 | 
            -
                    $stdout = StringIO.new
         | 
| 136 | 
            -
                    $stderr = StringIO.new
         | 
| 137 | 
            -
                    ActiveRecord::Tasks::DatabaseTasks.root = Mimi.app_root_path
         | 
| 138 | 
            -
                    ActiveRecord::Tasks::DatabaseTasks.drop(Mimi::DB.active_record_config)
         | 
| 139 | 
            -
                    Mimi::DB.logger.debug "Mimi::DB.drop! out:#{$stdout.string}, err:#{$stderr.string}"
         | 
| 140 | 
            -
                  ensure
         | 
| 141 | 
            -
                    $stdout = original_stdout
         | 
| 142 | 
            -
                    $stderr = original_stderr
         | 
| 122 | 
            +
                    raise 'Not implemented'
         | 
| 143 123 | 
             
                  end
         | 
| 144 124 |  | 
| 145 125 | 
             
                  # Clears (but not drops) the database specified in the current configuration.
         | 
| 146 126 | 
             
                  #
         | 
| 147 127 | 
             
                  def clear!
         | 
| 148 | 
            -
                     | 
| 149 | 
            -
                     | 
| 150 | 
            -
             | 
| 151 | 
            -
             | 
| 152 | 
            -
                     | 
| 153 | 
            -
                    ActiveRecord::Tasks::DatabaseTasks.purge(Mimi::DB.active_record_config)
         | 
| 154 | 
            -
                    Mimi::DB.logger.debug "Mimi::DB.clear! out:#{$stdout.string}, err:#{$stderr.string}"
         | 
| 155 | 
            -
                  ensure
         | 
| 156 | 
            -
                    $stdout = original_stdout
         | 
| 157 | 
            -
                    $stderr = original_stderr
         | 
| 128 | 
            +
                    Mimi::DB.start
         | 
| 129 | 
            +
                    db_table_names.each do |table_name|
         | 
| 130 | 
            +
                      Mimi::DB.logger.debug "Mimi::DB dropping table: #{table_name}"
         | 
| 131 | 
            +
                      Mimi::DB.connection.drop_table(table_name)
         | 
| 132 | 
            +
                    end
         | 
| 158 133 | 
             
                  end
         | 
| 159 134 |  | 
| 160 135 | 
             
                  # Executes raw SQL, with variables interpolation.
         | 
| @@ -163,8 +138,31 @@ module Mimi | |
| 163 138 | 
             
                  #   Mimi::DB.execute('insert into table1 values(?, ?, ?)', 'foo', :bar, 123)
         | 
| 164 139 | 
             
                  #
         | 
| 165 140 | 
             
                  def execute(statement, *args)
         | 
| 166 | 
            -
                    sql =  | 
| 167 | 
            -
                     | 
| 141 | 
            +
                    sql = Sequel.fetch(statement, *args).sql
         | 
| 142 | 
            +
                    Mimi::DB.connection.run(sql)
         | 
| 143 | 
            +
                  end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                  # Starts a transaction and executes a given block within the transaction
         | 
| 146 | 
            +
                  #
         | 
| 147 | 
            +
                  # @param params [Hash] parameters to Sequel #transaction() method
         | 
| 148 | 
            +
                  #
         | 
| 149 | 
            +
                  def transaction(params = {}, &_block)
         | 
| 150 | 
            +
                    unless Mimi::DB.connection
         | 
| 151 | 
            +
                      raise 'Failed to start transaction, Mimi::DB.connection not available'
         | 
| 152 | 
            +
                    end
         | 
| 153 | 
            +
                    Mimi::DB.connection.transaction(params) { yield }
         | 
| 154 | 
            +
                  end
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                  # Executes a block with a given DB log level
         | 
| 157 | 
            +
                  #
         | 
| 158 | 
            +
                  # @param log_level [Symbol,nil] :debug, :info etc
         | 
| 159 | 
            +
                  #
         | 
| 160 | 
            +
                  def with_log_level(log_level, &_block)
         | 
| 161 | 
            +
                    current_log_level = Mimi::DB.connection.sql_log_level
         | 
| 162 | 
            +
                    Mimi::DB.connection.sql_log_level = log_level
         | 
| 163 | 
            +
                    yield
         | 
| 164 | 
            +
                  ensure
         | 
| 165 | 
            +
                    Mimi::DB.connection.sql_log_level = current_log_level
         | 
| 168 166 | 
             
                  end
         | 
| 169 167 | 
             
                end # module Helpers
         | 
| 170 168 |  | 
    
        data/lib/mimi/db/lock.rb
    CHANGED
    
    | @@ -49,6 +49,10 @@ module Mimi | |
| 49 49 | 
             
                  end
         | 
| 50 50 |  | 
| 51 51 | 
             
                  def lock!(name, opts = {}, &block)
         | 
| 52 | 
            +
                    raise 'Not implemented'
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                    # FIXME: migrate Mimi::DB::Lock to Sequel
         | 
| 55 | 
            +
             | 
| 52 56 | 
             
                    opts = Mimi::DB::Lock.module_options[:default_lock_options].merge(opts.dup)
         | 
| 53 57 | 
             
                    adapter_name = ActiveRecord::Base.connection.adapter_name.downcase.to_sym
         | 
| 54 58 | 
             
                    case adapter_name
         | 
| @@ -0,0 +1,64 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # NOTE: this is the way to create an abstract class that inherits from Sequel::Model
         | 
| 4 | 
            +
            Mimi::DB::Model = Class.new(Sequel::Model)
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Mimi
         | 
| 7 | 
            +
              module DB
         | 
| 8 | 
            +
                class Model
         | 
| 9 | 
            +
                  include Mimi::DB::Dictate
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  self.require_valid_table = false
         | 
| 12 | 
            +
                  plugin :timestamps, create: :created_at, update: :updated_at, update_on_create: true
         | 
| 13 | 
            +
                  plugin :validation_helpers
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  # Keeps messages as error types, not human readable strings
         | 
| 16 | 
            +
                  #
         | 
| 17 | 
            +
                  def default_validation_helpers_options(type)
         | 
| 18 | 
            +
                    { message: type }
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def before_validation
         | 
| 22 | 
            +
                    super
         | 
| 23 | 
            +
                    call_hooks(:before_validation)
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  # Defines a hook the ActiveRecord way
         | 
| 27 | 
            +
                  #
         | 
| 28 | 
            +
                  # Example:
         | 
| 29 | 
            +
                  #
         | 
| 30 | 
            +
                  #   class A < Mimi::DB::Model
         | 
| 31 | 
            +
                  #     before_validation :set_detaults
         | 
| 32 | 
            +
                  #
         | 
| 33 | 
            +
                  #     def set_defaults
         | 
| 34 | 
            +
                  #       self.name = "John Doe"
         | 
| 35 | 
            +
                  #     end
         | 
| 36 | 
            +
                  #   end
         | 
| 37 | 
            +
                  #
         | 
| 38 | 
            +
                  def self.before_validation(method = nil, &block)
         | 
| 39 | 
            +
                    if method && block
         | 
| 40 | 
            +
                      raise ArgumentError, '.before_validation() cannot accept both method and a block'
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
                    block = -> { send(method) } if method
         | 
| 43 | 
            +
                    register_hook(:before_validation, block)
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  private
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def self.registered_hooks(name)
         | 
| 49 | 
            +
                    @registered_hooks ||= {}
         | 
| 50 | 
            +
                    @registered_hooks[name] ||= []
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  def self.register_hook(name, block)
         | 
| 54 | 
            +
                    registered_hooks(name) << block
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  def call_hooks(name)
         | 
| 58 | 
            +
                    self.class.registered_hooks(name).each do |block|
         | 
| 59 | 
            +
                      instance_eval(&block)
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                end # class Model
         | 
| 63 | 
            +
              end # module DB
         | 
| 64 | 
            +
            end # module Mimi
         | 
    
        data/lib/mimi/db/version.rb
    CHANGED
    
    
    
        data/lib/mimi/db.rb
    CHANGED
    
    | @@ -1,5 +1,5 @@ | |
| 1 1 | 
             
            require 'mimi/core'
         | 
| 2 | 
            -
            require ' | 
| 2 | 
            +
            require 'sequel'
         | 
| 3 3 |  | 
| 4 4 | 
             
            module Mimi
         | 
| 5 5 | 
             
              module DB
         | 
| @@ -13,12 +13,8 @@ module Mimi | |
| 13 13 | 
             
                  db_port: nil,
         | 
| 14 14 | 
             
                  db_username: nil,
         | 
| 15 15 | 
             
                  db_password: nil,
         | 
| 16 | 
            -
                  db_log_level: : | 
| 17 | 
            -
                  db_pool: 15 | 
| 18 | 
            -
                  db_primary_key_cockroachdb: nil,
         | 
| 19 | 
            -
                  db_primary_key_postgresql: nil,
         | 
| 20 | 
            -
                  db_primary_key_mysql: nil,
         | 
| 21 | 
            -
                  db_primary_key_sqlite3: nil
         | 
| 16 | 
            +
                  db_log_level: :debug,
         | 
| 17 | 
            +
                  db_pool: 15
         | 
| 22 18 | 
             
                  # db_encoding:
         | 
| 23 19 | 
             
                )
         | 
| 24 20 |  | 
| @@ -58,45 +54,75 @@ module Mimi | |
| 58 54 | 
             
                    },
         | 
| 59 55 | 
             
                    db_log_level: {
         | 
| 60 56 | 
             
                      desc: 'Logging level for database layer ("debug", "info" etc)',
         | 
| 61 | 
            -
                      default: ' | 
| 57 | 
            +
                      default: 'debug'
         | 
| 62 58 | 
             
                    }
         | 
| 63 59 | 
             
                  }
         | 
| 64 60 | 
             
                end
         | 
| 65 61 |  | 
| 66 62 | 
             
                def self.configure(*)
         | 
| 67 63 | 
             
                  super
         | 
| 68 | 
            -
                   | 
| 69 | 
            -
             | 
| 70 | 
            -
                   | 
| 71 | 
            -
             | 
| 72 | 
            -
                  # TODO: test and remove deprectated ...
         | 
| 73 | 
            -
                  # ActiveRecord::Base.raise_in_transactional_callbacks = true
         | 
| 64 | 
            +
                  if Mimi.const_defined?(:Application)
         | 
| 65 | 
            +
                    @logger = Mimi::Application.logger
         | 
| 66 | 
            +
                  end
         | 
| 74 67 | 
             
                end
         | 
| 75 68 |  | 
| 76 69 | 
             
                def self.logger
         | 
| 77 | 
            -
                  @logger ||= Mimi::Logger.new | 
| 70 | 
            +
                  @logger ||= Mimi::Logger.new
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                # Returns active DB connection
         | 
| 74 | 
            +
                #
         | 
| 75 | 
            +
                # @return [Sequel::<...>::Database]
         | 
| 76 | 
            +
                #
         | 
| 77 | 
            +
                def self.connection
         | 
| 78 | 
            +
                  @connection
         | 
| 78 79 | 
             
                end
         | 
| 79 80 |  | 
| 80 81 | 
             
                def self.start
         | 
| 81 | 
            -
                  ActiveRecord::Base.establish_connection(:default)
         | 
| 82 82 | 
             
                  Mimi::DB::Extensions.start
         | 
| 83 | 
            -
                   | 
| 83 | 
            +
                  @connection = Sequel.connect(sequel_config)
         | 
| 84 84 | 
             
                  Mimi.require_files(module_options[:require_files]) if module_options[:require_files]
         | 
| 85 85 | 
             
                  super
         | 
| 86 86 | 
             
                end
         | 
| 87 87 |  | 
| 88 | 
            -
                 | 
| 88 | 
            +
                # Returns a standard Sequel adapter name converted from any variation of adapter names.
         | 
| 89 | 
            +
                #
         | 
| 90 | 
            +
                # @example
         | 
| 91 | 
            +
                #   sequel_config_canonical_adapter_name(:sqlite3) # => 'sqlite'
         | 
| 92 | 
            +
                #
         | 
| 93 | 
            +
                # @param adapter_name [String,Symbol]
         | 
| 94 | 
            +
                # @return [String]
         | 
| 95 | 
            +
                #
         | 
| 96 | 
            +
                def self.sequel_config_canonical_adapter_name(adapter_name)
         | 
| 97 | 
            +
                  case adapter_name.to_s.downcase
         | 
| 98 | 
            +
                  when 'sqlite', 'sqlite3'
         | 
| 99 | 
            +
                    'sqlite'
         | 
| 100 | 
            +
                  when 'postgres', 'postgresql'
         | 
| 101 | 
            +
                    'postgres'
         | 
| 102 | 
            +
                  when 'cockroach', 'cockroachdb'
         | 
| 103 | 
            +
                    'cockroachdb'
         | 
| 104 | 
            +
                  else
         | 
| 105 | 
            +
                    adapter_name.to_s.downcase
         | 
| 106 | 
            +
                  end
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                # Returns Sequel connection parameters
         | 
| 110 | 
            +
                #
         | 
| 111 | 
            +
                # @return [Hash]
         | 
| 112 | 
            +
                #
         | 
| 113 | 
            +
                def self.sequel_config
         | 
| 89 114 | 
             
                  {
         | 
| 90 | 
            -
                    adapter: module_options[:db_adapter],
         | 
| 115 | 
            +
                    adapter: sequel_config_canonical_adapter_name(module_options[:db_adapter]),
         | 
| 91 116 | 
             
                    database: module_options[:db_database],
         | 
| 92 117 | 
             
                    host: module_options[:db_host],
         | 
| 93 118 | 
             
                    port: module_options[:db_port],
         | 
| 94 | 
            -
                     | 
| 119 | 
            +
                    user: module_options[:db_username],
         | 
| 95 120 | 
             
                    password: module_options[:db_password],
         | 
| 96 121 | 
             
                    encoding: module_options[:db_encoding],
         | 
| 97 | 
            -
                     | 
| 98 | 
            -
                     | 
| 99 | 
            -
             | 
| 122 | 
            +
                    max_connections: module_options[:db_pool],
         | 
| 123 | 
            +
                    sql_log_level: module_options[:db_log_level],
         | 
| 124 | 
            +
                    logger: logger
         | 
| 125 | 
            +
                  }
         | 
| 100 126 | 
             
                end
         | 
| 101 127 | 
             
              end # module DB
         | 
| 102 128 | 
             
            end # module Mimi
         | 
| @@ -106,3 +132,4 @@ require_relative 'db/extensions' | |
| 106 132 | 
             
            require_relative 'db/helpers'
         | 
| 107 133 | 
             
            require_relative 'db/foreign_key'
         | 
| 108 134 | 
             
            require_relative 'db/dictate'
         | 
| 135 | 
            +
            require_relative 'db/model'
         | 
    
        data/lib/tasks/db.rake
    CHANGED
    
    | @@ -40,17 +40,22 @@ namespace :db do | |
| 40 40 | 
             
              namespace :migrate do
         | 
| 41 41 | 
             
                desc 'Migrate database (seeds only)'
         | 
| 42 42 | 
             
                task seeds: :"db:start" do
         | 
| 43 | 
            +
                  logger.info "* Processing database seeds: #{Mimi::DB.module_options[:db_database]}"
         | 
| 44 | 
            +
                  t_start = Time.now
         | 
| 43 45 | 
             
                  seeds = Pathname.glob(Mimi.app_path_to('db', 'seeds', '**', '*.rb')).sort
         | 
| 44 46 | 
             
                  seeds.each do |seed_filename|
         | 
| 45 | 
            -
                    logger.info " | 
| 47 | 
            +
                    logger.info "-- Processing seed: #{seed_filename}"
         | 
| 46 48 | 
             
                    load seed_filename
         | 
| 47 49 | 
             
                  end
         | 
| 50 | 
            +
                  logger.info '* Finished processing database seeds (%.3fs)' % (Time.now - t_start)
         | 
| 48 51 | 
             
                end
         | 
| 49 52 |  | 
| 50 53 | 
             
                desc 'Migrate database (schema only)'
         | 
| 51 54 | 
             
                task schema: :"db:start" do
         | 
| 52 55 | 
             
                  logger.info "* Updating database schema: #{Mimi::DB.module_options[:db_database]}"
         | 
| 56 | 
            +
                  t_start = Time.now
         | 
| 53 57 | 
             
                  Mimi::DB.update_schema!(destructive: true)
         | 
| 58 | 
            +
                  logger.info '* Finished updating database schema (%.3fs)' % (Time.now - t_start)
         | 
| 54 59 | 
             
                end
         | 
| 55 60 |  | 
| 56 61 | 
             
                namespace :schema do
         | 
| @@ -88,6 +93,9 @@ namespace :db do | |
| 88 93 | 
             
                    diff[:drop_tables].each do |t|
         | 
| 89 94 | 
             
                      puts "-- DROP table: #{t}"
         | 
| 90 95 | 
             
                    end
         | 
| 96 | 
            +
                    if diff[:add_tables].empty? && diff[:change_tables].empty? && diff[:drop_tables].empty?
         | 
| 97 | 
            +
                      logger.info '* Diff database schema: no changes detected'
         | 
| 98 | 
            +
                    end
         | 
| 91 99 | 
             
                  end
         | 
| 92 100 | 
             
                end
         | 
| 93 101 | 
             
              end
         | 
    
        data/mimi-db.gemspec
    CHANGED
    
    | @@ -29,7 +29,7 @@ Gem::Specification.new do |spec| | |
| 29 29 |  | 
| 30 30 | 
             
              spec.add_dependency 'mimi-core', '~> 0.2'
         | 
| 31 31 | 
             
              spec.add_dependency 'mimi-logger', '~> 0.2'
         | 
| 32 | 
            -
              spec.add_dependency ' | 
| 32 | 
            +
              spec.add_dependency 'sequel', '~> 5.8'
         | 
| 33 33 |  | 
| 34 34 | 
             
              spec.add_development_dependency 'bundler', '~> 1.11'
         | 
| 35 35 | 
             
              spec.add_development_dependency 'rake', '~> 10.0'
         |