Class: ActiveRecord::Refined::Dialect
- Inherits:
-
Object
- Object
- ActiveRecord::Refined::Dialect
- Defined in:
- lib/active_record/refined/dialect.rb,
lib/active_record/refined/dialect/mysql.rb,
lib/active_record/refined/dialect/oracle.rb,
lib/active_record/refined/dialect/sqlite.rb,
lib/active_record/refined/dialect/mariadb.rb,
lib/active_record/refined/dialect/postgresql.rb,
lib/active_record/refined/dialect/sql_server.rb,
lib/active_record/refined/dialect/mysql_compat.rb
Overview
One class per family of SQL spellings, resolved from the model's adapter and asked, rather than branched on, for whatever a query builds differently from one database to the next. The base is the standard spelling an unclassified adapter keeps; each subclass overrides only where its family departs from it.
The families are loaded as they are met: an application on PostgreSQL never loads the Oracle class, whose adapter it will never resolve to.
Defined Under Namespace
Classes: Mariadb, Mysql, MysqlCompat, Oracle, Postgresql, SqlServer, Sqlite
Constant Summary collapse
- FUNCTIONS =
The scalar and datetime functions a family spells differently, or has none of. A name it does not list it spells like the method, upper cased; a nil says it has no equivalent, and the block raises. The base -- an unclassified adapter -- keeps every standard name.
{}.freeze
Class Method Summary collapse
-
.for(model) ⇒ Object
The dialect a model's queries are built for.
-
.register(adapter, dialect = nil, &block) ⇒ Object
Names an adapter's dialect.
Instance Method Summary collapse
-
#add_interval(date, amount, unit, subtract, _date_only) ⇒ Object
A date moved by a duration:
:due_on + 3.days. -
#bit_count(_expr, model) ⇒ Object
BIT_COUNT of a number.
-
#bitwise_xor(left, right) ⇒ Object
XOR, which no two families spell alike.
-
#check_json_aggregate_window(_source, _model) ⇒ Object
A family that cannot take these two as window functions refuses one.
-
#check_lateral(_model) ⇒ Object
A lateral join is allowed to stand unless the family refuses it here.
-
#check_string_aggregate_window(_model) ⇒ Object
A family that cannot take string_agg as a window function refuses one.
-
#collate(operand, name, _model) ⇒ Object
COLLATE, which every family spells
expr COLLATE name-- the name a bare identifier, no Arel node for it. - #datetime_precision_supported? ⇒ Boolean
-
#excluded(column, _model) ⇒ Object
The row an upsert could not insert.
- #extract_supported? ⇒ Boolean
-
#filter_supported? ⇒ Boolean
The FILTER clause, which restricts an aggregate to the rows a condition holds for.
- #full_outer_join_supported? ⇒ Boolean
- #function_name(name, model) ⇒ Object
-
#grouping_by_with_rollup? ⇒ Boolean
The MySQL family spells rollup WITH ROLLUP, trailing the group list rather than wrapping a list of its own, and overrides.
-
#grouping_supported?(_kind) ⇒ Boolean
--- Grouping.
-
#json_aggregate_filter_supported? ⇒ Boolean
These two keep a NULL as JSON null rather than passing over it, so the CASE that stands in for FILTER would leave one in the document; a family without FILTER for them refuses it.
-
#json_aggregate_name(kind) ⇒ Object
json_arrayagg / json_objectagg gather rows into a document.
-
#json_argument(value, _model) ⇒ Object
A Ruby document or boolean written where one of the JSON functions wants JSON.
-
#json_build(kind, keys, args, _model) ⇒ Object
json_array / json_object built in the row.
-
#json_build_argument(value, model) ⇒ Object
A document or boolean built into json_array/json_object.
- #json_contains(_document, _json, model) ⇒ Object
-
#json_document_value?(value) ⇒ Boolean
--- Writing JSON.
- #json_has_key(document, _name, path, _model) ⇒ Object
- #json_keys(document, _model) ⇒ Object
-
#json_list_by_element? ⇒ Boolean
A JSON value in an IN list or a range is compared per element on the MySQL family, which overrides; the standard leaves it to the IN.
-
#json_literal(_json, model) ⇒ Object
A Ruby value on the JSON side of a comparison belongs to a JSON type; a family without one refuses it.
-
#json_path(document, dollar_path, _steps, json_value, _model) ⇒ Object
dig / dig_text.
-
#json_remove(document, dollar_paths, _steps, _model) ⇒ Object
except: removing keys.
-
#json_set(document, _steps, dollar_path, value, expression, model) ⇒ Object
bury: setting a value at a path.
- #quantifiers_supported? ⇒ Boolean
-
#string_agg(operand, separator, orders, _string, model) ⇒ Object
string_agg: the strings of a group joined into one.
-
#truth_value(operand, value, negated, _model) ⇒ Object
true? / false? and their negations.
Class Method Details
.for(model) ⇒ Object
The dialect a model's queries are built for. An adapter nobody has registered keeps the standard spellings and is left to say for itself what it cannot do. The instances are cached by class rather than by adapter, so a re-registration takes effect on the next query.
56 57 58 59 |
# File 'lib/active_record/refined/dialect.rb', line 56 def for(model) klass = class_for(model.connection_db_config.adapter, model) @instances.compute_if_absent(klass) { klass.new } end |
.register(adapter, dialect = nil, &block) ⇒ Object
Names an adapter's dialect. An adapter's gem, or an application, registers its own -- a Dialect subclass overriding only where its family departs from the standard:
ActiveRecord::Refined::Dialect.register("exampledb", ExampleDialect)
A block registers an adapter whose dialect only the connection can name, as mysql2's is either MySQL's or MariaDB's: it receives the model and returns the class. The built-in families register with blocks too, which is what leaves each autoloaded until an adapter first resolves to it.
44 45 46 47 48 49 |
# File 'lib/active_record/refined/dialect.rb', line 44 def register(adapter, dialect = nil, &block) unless dialect || block raise ArgumentError, "register takes a dialect class or a block" end @registry[adapter.to_s] = dialect || block end |
Instance Method Details
#add_interval(date, amount, unit, subtract, _date_only) ⇒ Object
A date moved by a duration: :due_on + 3.days. The standard adds an
interval literal, x + INTERVAL '3' DAY, which PostgreSQL and the
MySQL family both read; SQLite, SQL Server and Oracle each spell the
move their own way and override. The amount is a whole number and
the unit one of six names, both checked by the node, so both are
written into the SQL as they are. date_only says the operand is a
date rather than a datetime, which only SQLite has to be told.
154 155 156 157 158 |
# File 'lib/active_record/refined/dialect.rb', line 154 def add_interval(date, amount, unit, subtract, _date_only) interval = Arel::Nodes::SqlLiteral.new("INTERVAL '#{amount}' #{unit.to_s.upcase}") Arel::Nodes::Grouping.new( Arel::Nodes::InfixOperation.new(subtract ? :- : :+, date, interval)) end |
#bit_count(_expr, model) ⇒ Object
BIT_COUNT of a number. The standard has no equivalent; the families that do override.
116 117 118 119 |
# File 'lib/active_record/refined/dialect.rb', line 116 def bit_count(_expr, model) raise NotImplementedError, "bit_count has no equivalent on #{model.connection_db_config.adapter}" end |
#bitwise_xor(left, right) ⇒ Object
XOR, which no two families spell alike. The standard is the two operations it is made of, naming each operand twice, as SQLite needs; PostgreSQL and the MySQL family have an operator and override.
163 164 165 166 167 |
# File 'lib/active_record/refined/dialect.rb', line 163 def bitwise_xor(left, right) Arel::Nodes::Subtraction.new( Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseOr.new(left, right)), Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseAnd.new(left, right))) end |
#check_json_aggregate_window(_source, _model) ⇒ Object
A family that cannot take these two as window functions refuses one.
263 |
# File 'lib/active_record/refined/dialect.rb', line 263 def check_json_aggregate_window(_source, _model); end |
#check_lateral(_model) ⇒ Object
A lateral join is allowed to stand unless the family refuses it here.
110 |
# File 'lib/active_record/refined/dialect.rb', line 110 def check_lateral(_model); end |
#check_string_aggregate_window(_model) ⇒ Object
A family that cannot take string_agg as a window function refuses one.
280 |
# File 'lib/active_record/refined/dialect.rb', line 280 def check_string_aggregate_window(_model); end |
#collate(operand, name, _model) ⇒ Object
COLLATE, which every family spells expr COLLATE name -- the name a
bare identifier, no Arel node for it. Written bare it has to be a
plain one, so it is checked here; PostgreSQL quotes it and widens what
it takes, overriding. PostgreSQL also folds an unquoted name to lower
case, where its built-in names are upper -- "C", "POSIX" -- another
reason it quotes rather than inheriting this.
141 142 143 144 145 |
# File 'lib/active_record/refined/dialect.rb', line 141 def collate(operand, name, _model) AST.check_name(name, AST::COLLATION_NAME, "collation name") Arel::Nodes::InfixOperation.new( "COLLATE", operand, Arel::Nodes::SqlLiteral.new(name)) end |
#datetime_precision_supported? ⇒ Boolean
99 |
# File 'lib/active_record/refined/dialect.rb', line 99 def datetime_precision_supported? = true |
#excluded(column, _model) ⇒ Object
The row an upsert could not insert. PostgreSQL and SQLite name it; MySQL spells the same thing VALUES(column) and overrides.
123 124 125 |
# File 'lib/active_record/refined/dialect.rb', line 123 def excluded(column, _model) AST::Column.new(:excluded, column) end |
#extract_supported? ⇒ Boolean
100 |
# File 'lib/active_record/refined/dialect.rb', line 100 def extract_supported? = true |
#filter_supported? ⇒ Boolean
The FILTER clause, which restricts an aggregate to the rows a condition holds for. A family without it gets the CASE that means the same, built by the aggregate node.
107 |
# File 'lib/active_record/refined/dialect.rb', line 107 def filter_supported? = true |
#full_outer_join_supported? ⇒ Boolean
102 |
# File 'lib/active_record/refined/dialect.rb', line 102 def full_outer_join_supported? = true |
#function_name(name, model) ⇒ Object
91 92 93 94 95 96 97 |
# File 'lib/active_record/refined/dialect.rb', line 91 def function_name(name, model) functions = self.class::FUNCTIONS return name.to_s.upcase unless functions.key?(name) functions.fetch(name) || raise(NotImplementedError, "#{name} has no equivalent on #{model.connection_db_config.adapter}") end |
#grouping_by_with_rollup? ⇒ Boolean
The MySQL family spells rollup WITH ROLLUP, trailing the group list rather than wrapping a list of its own, and overrides.
293 |
# File 'lib/active_record/refined/dialect.rb', line 293 def grouping_by_with_rollup? = false |
#grouping_supported?(_kind) ⇒ Boolean
--- Grouping. GROUPING SETS, ROLLUP and CUBE, which the standard has none of; PostgreSQL has all three and the MySQL family rollup alone.
289 |
# File 'lib/active_record/refined/dialect.rb', line 289 def grouping_supported?(_kind) = false |
#json_aggregate_filter_supported? ⇒ Boolean
These two keep a NULL as JSON null rather than passing over it, so the CASE that stands in for FILTER would leave one in the document; a family without FILTER for them refuses it.
260 |
# File 'lib/active_record/refined/dialect.rb', line 260 def json_aggregate_filter_supported? = true |
#json_aggregate_name(kind) ⇒ Object
json_arrayagg / json_objectagg gather rows into a document. The standard names are JSON_ARRAYAGG and JSON_OBJECTAGG; SQLite and PostgreSQL have their own and override.
253 254 255 |
# File 'lib/active_record/refined/dialect.rb', line 253 def json_aggregate_name(kind) kind == :arrayagg ? "JSON_ARRAYAGG" : "JSON_OBJECTAGG" end |
#json_argument(value, _model) ⇒ Object
A Ruby document or boolean written where one of the JSON functions wants JSON. The standard marks the literal with JSON_EXTRACT($); SQLite has json() and Oracle FORMAT JSON, and both override.
213 214 215 216 |
# File 'lib/active_record/refined/dialect.rb', line 213 def json_argument(value, _model) json = Arel::Nodes.build_quoted(JSON.generate(value)) Arel::Nodes::NamedFunction.new("JSON_EXTRACT", [json, Arel::Nodes.build_quoted("$")]) end |
#json_build(kind, keys, args, _model) ⇒ Object
json_array / json_object built in the row. The standard says JSON_ARRAY and JSON_OBJECT with the values (and keys alternating); PostgreSQL has the jsonb_build_* pair and Oracle a keyword syntax, and both override.
239 240 241 242 |
# File 'lib/active_record/refined/dialect.rb', line 239 def json_build(kind, keys, args, _model) Arel::Nodes::NamedFunction.new( kind == :array ? "JSON_ARRAY" : "JSON_OBJECT", json_build_body(kind, keys, args)) end |
#json_build_argument(value, model) ⇒ Object
A document or boolean built into json_array/json_object. The standard marks it JSON as bury does; PostgreSQL casts to jsonb and overrides.
246 247 248 |
# File 'lib/active_record/refined/dialect.rb', line 246 def json_build_argument(value, model) json_argument(value, model) end |
#json_contains(_document, _json, model) ⇒ Object
190 191 192 193 |
# File 'lib/active_record/refined/dialect.rb', line 190 def json_contains(_document, _json, model) raise NotImplementedError, "contains? has no equivalent on #{model.connection_db_config.adapter}" end |
#json_document_value?(value) ⇒ Boolean
--- Writing JSON. A Ruby document or boolean has to be told apart from a bare scalar, and embedded as the JSON it spells.
206 207 208 |
# File 'lib/active_record/refined/dialect.rb', line 206 def json_document_value?(value) value.is_a?(::Hash) || value.is_a?(::Array) || value == true || value == false end |
#json_has_key(document, _name, path, _model) ⇒ Object
195 196 197 |
# File 'lib/active_record/refined/dialect.rb', line 195 def json_has_key(document, _name, path, _model) Arel::Nodes::NamedFunction.new("json_type", [document, path]).not_eq(nil) end |
#json_keys(document, _model) ⇒ Object
199 200 201 |
# File 'lib/active_record/refined/dialect.rb', line 199 def json_keys(document, _model) Arel::Nodes::NamedFunction.new("JSON_KEYS", [document]) end |
#json_list_by_element? ⇒ Boolean
A JSON value in an IN list or a range is compared per element on the MySQL family, which overrides; the standard leaves it to the IN.
284 |
# File 'lib/active_record/refined/dialect.rb', line 284 def json_list_by_element? = false |
#json_literal(_json, model) ⇒ Object
A Ruby value on the JSON side of a comparison belongs to a JSON type; a family without one refuses it.
184 185 186 187 188 |
# File 'lib/active_record/refined/dialect.rb', line 184 def json_literal(_json, model) raise NotImplementedError, "a JSON comparison has no equivalent on " \ "#{model.connection_db_config.adapter}; dig_text gives the value" end |
#json_path(document, dollar_path, _steps, json_value, _model) ⇒ Object
dig / dig_text. The standard is SQLite's -> and ->>, whose ->> keeps the value's type, so dig_text casts to text for a portable comparison.
174 175 176 177 178 179 180 |
# File 'lib/active_record/refined/dialect.rb', line 174 def json_path(document, dollar_path, _steps, json_value, _model) extracted = Arel::Nodes::InfixOperation.new( json_value ? :"->" : :"->>", document, Arel::Nodes.build_quoted(dollar_path)) return extracted if json_value Arel::Nodes::NamedFunction.new( "CAST", [Arel::Nodes::As.new(extracted, Arel::Nodes::SqlLiteral.new("text"))]) end |
#json_remove(document, dollar_paths, _steps, _model) ⇒ Object
except: removing keys. The standard removes a path apiece with JSON_REMOVE; PostgreSQL subtracts an array of keys and Oracle removes through JSON_TRANSFORM, and both override.
230 231 232 233 234 |
# File 'lib/active_record/refined/dialect.rb', line 230 def json_remove(document, dollar_paths, _steps, _model) Arel::Nodes::NamedFunction.new( "JSON_REMOVE", [document, *dollar_paths.map { |path| Arel::Nodes.build_quoted(path) }]) end |
#json_set(document, _steps, dollar_path, value, expression, model) ⇒ Object
bury: setting a value at a path. The standard is JSON_SET; PostgreSQL has jsonb_set and Oracle JSON_TRANSFORM, and both override.
220 221 222 223 224 225 |
# File 'lib/active_record/refined/dialect.rb', line 220 def json_set(document, _steps, dollar_path, value, expression, model) Arel::Nodes::NamedFunction.new( "JSON_SET", [document, Arel::Nodes.build_quoted(dollar_path), json_set_value(value, expression, model)]) end |
#quantifiers_supported? ⇒ Boolean
101 |
# File 'lib/active_record/refined/dialect.rb', line 101 def quantifiers_supported? = true |
#string_agg(operand, separator, orders, _string, model) ⇒ Object
string_agg: the strings of a group joined into one. The standard's is
LISTAGG(x, ', ') WITHIN GROUP (ORDER BY ...), which Oracle reads, and
the ORDER BY is not optional there: an aggregate asked for no order
is given the values' own, which costs the caller nothing to have.
PostgreSQL and SQLite carry the ORDER BY inside the call, SQL Server
takes the WITHIN GROUP only when there is an order, and the MySQL
family has GROUP_CONCAT; every one of them overrides. string says
the operand is a column declared one, which PostgreSQL alone asks.
273 274 275 276 277 |
# File 'lib/active_record/refined/dialect.rb', line 273 def string_agg(operand, separator, orders, _string, model) orders = [operand] if orders.empty? Arel.sql("LISTAGG(#{compile(operand, model)}, #{quote(separator, model)}) " \ "WITHIN GROUP (ORDER BY #{compile_list(orders, model)})") end |
#truth_value(operand, value, negated, _model) ⇒ Object
true? / false? and their negations. The standard spells them with the boolean IS [NOT] TRUE/FALSE, which keeps a NULL out of the plain form and in of the negation; a family without a boolean type overrides.
130 131 132 133 |
# File 'lib/active_record/refined/dialect.rb', line 130 def truth_value(operand, value, negated, _model) literal = value ? Arel::Nodes::True.new : Arel::Nodes::False.new Arel::Nodes::InfixOperation.new(negated ? "IS NOT" : "IS", operand, literal) end |