Class: Pluckr::Compiler::Sql
- Inherits:
-
Object
- Object
- Pluckr::Compiler::Sql
- Defined in:
- lib/pluckr/compiler/sql.rb
Overview
Turns a Schema::Scope into SQL.
Everything is built with Arel and ActiveRecord, so PostgreSQL, MySQL and SQLite all get correctly quoted statements from the same code path.
Rules of the MVP:
* plain fields -> selected columns, never `SELECT users.*`
* singular associations -> LEFT OUTER JOIN + hidden presence marker
* exists -> correlated EXISTS subquery
* count/sum/min/max -> scalar subquery (never JOIN + GROUP BY)
The compiler only reads the AST. It never touches DSL state.
Defined Under Namespace
Classes: Build
Instance Attribute Summary collapse
-
#schema ⇒ Object
readonly
Returns the value of attribute schema.
-
#source_model ⇒ Object
readonly
Returns the value of attribute source_model.
Instance Method Summary collapse
-
#aggregate_only_sql ⇒ Object
Single-statement SQL for aggregate-only (dashboard and batch) queries.
-
#apply(relation) ⇒ Object
Applies Pluckr projections/joins on top of a plain ActiveRecord relation.
- #connection ⇒ Object
-
#initialize(schema:, source_model: nil) ⇒ Sql
constructor
A new instance of Sql.
-
#validate_connections! ⇒ Object
A single statement cannot span two databases.
Constructor Details
#initialize(schema:, source_model: nil) ⇒ Sql
Returns a new instance of Sql.
20 21 22 23 |
# File 'lib/pluckr/compiler/sql.rb', line 20 def initialize(schema:, source_model: nil) @schema = schema @source_model = source_model end |
Instance Attribute Details
#schema ⇒ Object (readonly)
Returns the value of attribute schema.
18 19 20 |
# File 'lib/pluckr/compiler/sql.rb', line 18 def schema @schema end |
#source_model ⇒ Object (readonly)
Returns the value of attribute source_model.
18 19 20 |
# File 'lib/pluckr/compiler/sql.rb', line 18 def source_model @source_model end |
Instance Method Details
#aggregate_only_sql ⇒ Object
Single-statement SQL for aggregate-only (dashboard and batch) queries.
38 39 40 41 42 43 44 45 46 |
# File 'lib/pluckr/compiler/sql.rb', line 38 def aggregate_only_sql validate_connections! parts = schema.nodes.map.with_index(1) do |node, index| "#{scalar_subquery(node, index)} AS #{quote_alias(node.output)}" end "SELECT #{parts.join(", ")}" end |
#apply(relation) ⇒ Object
Applies Pluckr projections/joins on top of a plain ActiveRecord relation.
Compiled fresh every time: scope: callables may depend on runtime state
and the compiler must stay free of shared mutable state.
29 30 31 32 33 34 35 |
# File 'lib/pluckr/compiler/sql.rb', line 29 def apply(relation) validate_connections! build = Build.new walk(build, schema, [], source_model.arel_table) relation.select(*build.projections).joins(build.joins) end |
#connection ⇒ Object
48 49 50 |
# File 'lib/pluckr/compiler/sql.rb', line 48 def connection connection_model.connection end |
#validate_connections! ⇒ Object
A single statement cannot span two databases. Relation-rooted nodes are checked when they are added, in Pluckr::Batch.
54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/pluckr/compiler/sql.rb', line 54 def validate_connections! models = ([source_model] + independent_models).compact.uniq return if models.size < 2 pool = models.first.connection_pool offender = models.find { |model| model.connection_pool != pool } return unless offender raise ConfigurationError, "#{offender.name} and #{models.first.name} use different database connections, " \ "so they cannot be read in one statement" end |