Class: Exwiw::QueryAstBuilder
- Inherits:
-
Object
- Object
- Exwiw::QueryAstBuilder
- Defined in:
- lib/exwiw/query_ast_builder.rb
Instance Attribute Summary collapse
-
#dump_target ⇒ Object
readonly
Returns the value of attribute dump_target.
-
#table_by_name ⇒ Object
readonly
Returns the value of attribute table_by_name.
-
#table_name ⇒ Object
readonly
Returns the value of attribute table_name.
Class Method Summary collapse
- .run(table_name, table_by_name, dump_target, logger, allow_reverse: true, allow_forward: true) ⇒ Object
-
.scope_category(table_name, table_by_name, dump_target, logger) ⇒ Object
Scope-column mode classification for a single table.
-
.validate_scope!(tables, table_by_name, dump_target, logger) ⇒ Object
Strict pre-flight for scope-column mode: abort if any extractable table cannot be scoped, so an unscoped (potentially sensitive) table is never silently dumped in full.
Instance Method Summary collapse
-
#initialize(table_name, table_by_name, dump_target, logger, allow_reverse: true, allow_forward: true) ⇒ QueryAstBuilder
constructor
A new instance of QueryAstBuilder.
- #run ⇒ Object
-
#scope_category ⇒ Object
Classifier used by validate_scope! and mirrored by build_scoped below.
Constructor Details
#initialize(table_name, table_by_name, dump_target, logger, allow_reverse: true, allow_forward: true) ⇒ QueryAstBuilder
Returns a new instance of QueryAstBuilder.
39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/exwiw/query_ast_builder.rb', line 39 def initialize(table_name, table_by_name, dump_target, logger, allow_reverse: true, allow_forward: true) @table_name = table_name @table_by_name = table_by_name @dump_target = dump_target @logger = logger @allow_reverse = allow_reverse # @allow_forward gates the "scope via an indirectly-scoped belongs_to # parent" rescue (build_belongs_to_scoped_clause). Disabled while building a # parent/child subquery so a single forward hop never recurses into another # (which could loop on a belongs_to cycle). @allow_forward = allow_forward end |
Instance Attribute Details
#dump_target ⇒ Object (readonly)
Returns the value of attribute dump_target.
37 38 39 |
# File 'lib/exwiw/query_ast_builder.rb', line 37 def dump_target @dump_target end |
#table_by_name ⇒ Object (readonly)
Returns the value of attribute table_by_name.
37 38 39 |
# File 'lib/exwiw/query_ast_builder.rb', line 37 def table_by_name @table_by_name end |
#table_name ⇒ Object (readonly)
Returns the value of attribute table_name.
37 38 39 |
# File 'lib/exwiw/query_ast_builder.rb', line 37 def table_name @table_name end |
Class Method Details
.run(table_name, table_by_name, dump_target, logger, allow_reverse: true, allow_forward: true) ⇒ Object
5 6 7 |
# File 'lib/exwiw/query_ast_builder.rb', line 5 def self.run(table_name, table_by_name, dump_target, logger, allow_reverse: true, allow_forward: true) new(table_name, table_by_name, dump_target, logger, allow_reverse: allow_reverse, allow_forward: allow_forward).run end |
.scope_category(table_name, table_by_name, dump_target, logger) ⇒ Object
Scope-column mode classification for a single table. One of :exempt / :direct / :via_path / :referenced_by / :via_scoped_parent / :unscopable.
11 12 13 |
# File 'lib/exwiw/query_ast_builder.rb', line 11 def self.scope_category(table_name, table_by_name, dump_target, logger) new(table_name, table_by_name, dump_target, logger).scope_category end |
.validate_scope!(tables, table_by_name, dump_target, logger) ⇒ Object
Strict pre-flight for scope-column mode: abort if any extractable table cannot be scoped, so an unscoped (potentially sensitive) table is never silently dumped in full. No-op outside scope mode. ‘tables` is the set of dumpable configs (ignore:true tables are skipped — they are not extracted).
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/exwiw/query_ast_builder.rb', line 19 def self.validate_scope!(tables, table_by_name, dump_target, logger) return if dump_target.scope_column.nil? unscopable = tables.reject(&:ignore).select do |table| scope_category(table.name, table_by_name, dump_target, logger) == :unscopable end return if unscopable.empty? names = unscopable.map(&:name).sort.join(", ") raise ArgumentError, "scope-column mode: #{unscopable.size} table(s) cannot be scoped by " \ "'#{dump_target.scope_column}': #{names}. For each, add `scope_exempt: true` " \ "to export it in full, set `ignore: true` to skip it, or add a belongs_to path " \ "to a table that carries the scope column (use a per-table `scope_column` if the " \ "column name differs on that table)." end |
Instance Method Details
#run ⇒ Object
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/exwiw/query_ast_builder.rb', line 52 def run table = table_by_name.fetch(table_name) return build_scoped(table) if scope_mode? where_clauses = build_where_clauses(table, dump_target) join_clauses = build_join_clauses(table, table_by_name, dump_target) # Reverse / "referenced_by" extraction. A table with no belongs_to path to # the dump target produces no where/join clauses and would otherwise dump # every row (see the "no relation -> dump all" case). If an extractable # child table references it via a foreign key (e.g. active_storage_blobs is # referenced by active_storage_attachments.blob_id), constrain it to just # the referenced ids instead. Disabled (@allow_reverse=false) while building # a child's subquery, so this never recurses. if @allow_reverse && table.name != dump_target.table_name && where_clauses.empty? && join_clauses.empty? reverse_clause = build_referenced_by_clause(table) where_clauses.push(reverse_clause) if reverse_clause end QueryAst::Select.new.tap do |ast| ast.from(table.name) if table.rails_managed? ast.select_all! else ast.select(table.columns) end join_clauses.each { |join_clause| ast.join(join_clause) } where_clauses.each { |where_clause| ast.where(where_clause) } end end |
#scope_category ⇒ Object
Classifier used by validate_scope! and mirrored by build_scoped below.
377 378 379 380 381 382 383 384 385 386 |
# File 'lib/exwiw/query_ast_builder.rb', line 377 def scope_category table = table_by_name.fetch(table_name) return :exempt if scope_exempt?(table) return :direct if directly_scoped?(table) return :via_path if build_join_clauses_scoped(table).any? return :referenced_by if @allow_reverse && build_referenced_by_clause(table) return :via_scoped_parent if @allow_forward && build_belongs_to_scoped_clause(table) :unscopable end |