Class: Apartment::Adapters::Mysql2Adapter
- Inherits:
-
AbstractAdapter
- Object
- AbstractAdapter
- Apartment::Adapters::Mysql2Adapter
- Defined in:
- lib/apartment/adapters/mysql2_adapter.rb
Overview
v4 MySQL adapter using database-per-tenant isolation (mysql2 driver).
Resolves tenant-specific connection configs by setting the database key
to the environmentified tenant name. Lifecycle operations (create/drop)
execute DDL against the default connection.
Direct Known Subclasses
Instance Attribute Summary
Attributes inherited from AbstractAdapter
Instance Method Summary collapse
-
#current_db_role(connection) ⇒ Object
Returns MySQL's
role@hostform, for a policy that needs to name the executing account. -
#failsafe_error_classes ⇒ Object
The database-per-tenant missing-tenant error: connecting to a dropped database raises ActiveRecord::NoDatabaseError (MySQL error 1049) — an unambiguous signal.
-
#pinned_table_qualifier ⇒ Object
Pinned tables live in the default tenant's database; every tenant connection can reach them by database-qualifying the name.
- #resolve_connection_config(tenant, base_config: nil) ⇒ Object
- #shared_pinned_connection? ⇒ Boolean
-
#standard_privilege_statements(ctx, grant_to:, include_functions: true) ⇒ Object
MySQL has no ALTER DEFAULT PRIVILEGES.
Methods inherited from AbstractAdapter
#aborted_transaction?, #apply_pinned_qualification, #awaiting_own_qualification?, #check_pinned_subclass, #create, #default_tenant, #drop, #environmentify, #inheriting_descendants, #inherits_pinned_table?, #initialize, #migrate, #nearest_pinned_ancestor, #physical_tenant_name, #pinned_qualification_mutates?, #pinned_table_name_for, #process_excluded_models, #process_pinned_model, #process_pinned_models, #qualify_pinned_table_name, #qualify_pinned_table_name_prefix, #seed, #tenant_container_gone?, #unqualified_pinned_message, #unregistered_pinned_subclass?, #validated_connection_config, #verified_pinned_qualifier, #verify_pinned_qualification!, #warn_unqualified_descendants, #warn_unqualified_subclass, #warn_unregistered_pinned_subclasses
Constructor Details
This class inherits a constructor from Apartment::Adapters::AbstractAdapter
Instance Method Details
#current_db_role(connection) ⇒ Object
Returns MySQL's role@host form, for a policy that needs to name the
executing account. The statement builder above does not consume it: it assumes
the % host, and MySQL's GRANT syntax wants the halves quoted separately
('role'@'host'), which a whole role@host token cannot express.
71 72 73 |
# File 'lib/apartment/adapters/mysql2_adapter.rb', line 71 def current_db_role(connection) connection.select_value('SELECT CURRENT_USER()') end |
#failsafe_error_classes ⇒ Object
The database-per-tenant missing-tenant error: connecting to a dropped database raises ActiveRecord::NoDatabaseError (MySQL error 1049) — an unambiguous signal. It surfaces raw at query time, or wrapped in ApartmentError when ConnectionHandling resolves the pool (the dev-mode pending-migration check), so both are listed; #container_error? gates on the unwrapped NoDatabaseError. Inherited by TrilogyAdapter.
34 35 36 |
# File 'lib/apartment/adapters/mysql2_adapter.rb', line 34 def failsafe_error_classes [ActiveRecord::NoDatabaseError, Apartment::ApartmentError] end |
#pinned_table_qualifier ⇒ Object
Pinned tables live in the default tenant's database; every tenant connection can reach them by database-qualifying the name.
19 20 21 |
# File 'lib/apartment/adapters/mysql2_adapter.rb', line 19 def pinned_table_qualifier base_config['database'] end |
#resolve_connection_config(tenant, base_config: nil) ⇒ Object
23 24 25 26 |
# File 'lib/apartment/adapters/mysql2_adapter.rb', line 23 def resolve_connection_config(tenant, base_config: nil) config = base_config || send(:base_config) config.merge('database' => environmentify(tenant)) end |
#shared_pinned_connection? ⇒ Boolean
13 14 15 |
# File 'lib/apartment/adapters/mysql2_adapter.rb', line 13 def shared_pinned_connection? !Apartment.config.force_separate_pinned_pool end |
#standard_privilege_statements(ctx, grant_to:, include_functions: true) ⇒ Object
MySQL has no ALTER DEFAULT PRIVILEGES. ON db.* is pattern-based and covers
objects created later, so one statement in the first phase is the whole
policy and include_functions has nothing to control here.
grant_to takes bare role names and every grant lands on role@'%'. Splitting
an account on its last @ would be wrong, because me@localhost is itself a
legal MySQL username, so a value carrying @ is refused rather than guessed at.
A specific host is what a custom policy is for.
Branching on the phase by name, rather than falling out of a before_schema_load? guard, so the empty after-phase is a stated decision and an unrecognised phase raises. A silent nothing is the defect this whole design replaces: app_role's String form did nothing on two adapters and told nobody.
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/apartment/adapters/mysql2_adapter.rb', line 51 def standard_privilege_statements(ctx, grant_to:, include_functions: true) # rubocop:disable Lint/UnusedMethodArgument case ctx.phase when :before_schema_load roles = Array(grant_to) (roles) accounts = roles.map { |role| "#{ctx.connection.quote(role)}@'%'" }.join(', ') ["GRANT SELECT, INSERT, UPDATE, DELETE ON #{ctx.quoted_container}.* TO #{accounts}"] when :after_schema_load # Nothing to do: the grant above already covers tables the import and later # migrations create. [] else raise(Apartment::ConfigurationError, "Unknown privilege policy phase: #{ctx.phase.inspect}") end end |