influitive / apartment

Database multi-tenancy for Rack (and Rails) applications
2.66k stars 464 forks source link

v 1.2 - Newly created schema not copying public (parent) schema data type length #626

Closed joemsak closed 4 years ago

joemsak commented 4 years ago

Steps to reproduce

Using Apartment 1.2 on existing postgresql database Have a model with a simple t.string :name column no other modifiers Run Apartment::Tenant.create("schema_name") Inspect ActiveRecord::Base.connection.columns("public.table_name") => name's sql_type is "character varying(255)" Inspect ActiveRecord::Base.connection.columns("schema_name.table_name")

Expected behavior

=> name's sql_type is "character varying(255)"

Actual behavior

=> name's sql_type is "character varying"

System configuration

Rails 4.2 Ruby 2.3.8 Apartment 1.2

There is an INHERIT migration running which throws DatatypeMismatch error due to the new child schema's table not picking up the (255) character limit for the type

Postgresql 9.6 on AWS RDS

1.2

Require whichever elevator you're using below here...

#

require 'apartment/elevators/generic'

require 'apartment/elevators/domain'

require 'apartment/elevators/subdomain'

require 'apartment' unless Kannu.tenant.config.no_database? require 'apartment/elevators/host_hash' require 'apartment/adapters/postgresql_schema_from_sql_adapter' #

Apartment Configuration

# if Kadenze.kannu? && !Kadenze.config.no_database? Apartment.configure do |config|

These models will not be multi-tenanted,

# but remain in the global (public) namespace
#
# An example might be a Customer or Tenant model that stores each tenant information
# ex:
#
# config.excluded_models = %w{Tenant}
#
config.excluded_models = %w[
  User
  Identity
  Instructor
  Student
  Institution
  Role
  EmailPreference
  UsersRole
  Membership
  Translation
  AfterParty::TaskRecord
  ConfirmationToken
  StripeConnectState
]

# use postgres schemas?
config.use_schemas = true

# use raw SQL dumps for creating postgres schemas? (only appies with use_schemas set to true)
config.use_sql = true

# configure persistent schemas (E.g. hstore )
# config.persistent_schemas = %w{ hstore }

# add the Rails environment to database names?
# config.prepend_environment = true
# config.append_environment = true

# supply list of database names for migrations to run on
config.tenant_names = -> { Institution.pluck(:tenant_name) }

end end

Elevator Configuration

NOTE: This is specified in the application config to keep all of our middleware declarations in one place

Rails.application.config.middleware.use 'Apartment::Elevators::Generic', lambda { |request|

TODO: supply generic implementation

}

Rails.application.config.middleware.use 'Apartment::Elevators::Domain'

Rails.application.config.middleware.use 'Apartment::Elevators::Subdomain'


  * `use_schemas`: (`true` or `false`)

* Rails (or ActiveRecord) version:
4.2

* Ruby version:

2.3.8

I'm sure maybe I changed something, but I dont have a clue what, because the last tenant I created was fine, and now all newly created tenants are having this problem
joemsak commented 4 years ago

I have an SO question as well in case it helps with more context https://stackoverflow.com/questions/59668861/postgresql-9-6-with-multi-tenancy-inherit-says-child-table-has-column-with-dif

joemsak commented 4 years ago

Rooting around the commit history of the apartment initializer, I realized we've also had this laying around

# frozen_string_literal: true

require 'apartment/adapters/postgresql_adapter'

# Monkey patching Apartment to fix this:
# https://github.com/influitive/apartment/issues/532
module Apartment
  module Adapters
    class PostgresqlSchemaFromSqlAdapter < PostgresqlSchemaAdapter
      PSQL_DUMP_BLACKLISTED_STATEMENTS = [
        /SET search_path/i,                           # overridden later
        /SET lock_timeout/i,                          # new in postgresql 9.3
        /SET row_security/i,                          # new in postgresql 9.5
        /SET idle_in_transaction_session_timeout/i,   # new in postgresql 9.6
        /CREATE SCHEMA public/i                       # this schema already exists
      ].freeze

      def import_database_schema
        preserving_search_path do
          clone_pg_schema
          copy_schema_migrations
        end
      end

      def clone_pg_schema
        pg_schema_sql = patch_search_path(pg_dump_schema)
        Apartment.connection.execute(pg_schema_sql)
      end

      def preserving_search_path
        search_path = Apartment.connection.execute('show search_path').first['search_path']
        yield
        Apartment.connection.execute("set search_path = #{search_path}")
      end

      def patch_search_path(sql)
        search_path = "SET search_path = \"#{current}\", #{default_tenant};"

        swap_schema_qualifier(sql)
          .split("\n")
          .select { |line| check_input_against_regexps(line, PSQL_DUMP_BLACKLISTED_STATEMENTS).empty? }
          .prepend(search_path)
          .join("\n")
      end

      def swap_schema_qualifier(sql)
        sql.gsub(/#{default_tenant}\.\w*/) do |match|
          match.gsub(default_tenant, %("#{current}"))
        end
      end
    end
  end
  end

This has been here seemingly not causing issues for a year or so, and I don't know what it's really doing or is for, I haven't messed with it

joemsak commented 4 years ago

This one turned out to be totally my fault. Red herrings threw me off. It was a loading order dependency in my initializers. I was referencing Apartment::Tenant in some custom domain code before all other initializers, meaning the adapter would load and memoize with the default config options, and the apartment config initializer would not take over and set the PostgresqlSchemaFromSqlAdapter - once I fixed the ordering, and that adapter is set, the tenant inherits worked.