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,
lib/active_record/refined/dialect/mysqlish_json_functions.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. Where no spelling is anyone's standard -- most of JSON -- the base refuses instead, and every family carries its own.
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
Modules: MysqlishJsonFunctions 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 the names most families share and nils the ones that are one or two families' own: printf FORMAT, whose name MySQL hands to a different function entirely, RAND, DATE_TRUNC, NOW, LOG2 and the three bit aggregates. A family that has one of them says so in a table of its own, since the tables shadow rather than merge.
{ format: nil, rand: nil, date_trunc: nil, now: nil, log2: nil, bit_and: nil, bit_or: nil, bit_xor: nil, }.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. -
#array_comparisons_supported? ⇒ Boolean
The array comparisons -- @>, <@ and && against an array column.
-
#bit_count(_expr, model) ⇒ Object
BIT_COUNT of a number.
-
#bitwise_operators_supported? ⇒ Boolean
The bitwise operators, & | ^ << >> and ~.
-
#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
Whether the datetime value functions take a precision,
current_timestamp(3). -
#excluded(_column, model) ⇒ Object
The row an upsert could not insert.
-
#extract_supported? ⇒ Boolean
Whether the family has EXTRACT.
-
#filter_supported? ⇒ Boolean
The FILTER clause, which restricts an aggregate to the rows a condition holds for.
-
#full_outer_join_supported? ⇒ Boolean
Whether a FULL OUTER JOIN can be written.
-
#function_name(name, model) ⇒ String
The name this family spells a function with, asked for as the block builds the call; FUNCTIONS is where the answer is looked up.
-
#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
Whether the family has the kind of grouping asked for, one of
:grouping_sets,:rollupand:cube. -
#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
contains?: whether the document holds what is given.
-
#json_document_value?(value) ⇒ Boolean
The test the writing hooks share: a Hash, an Array or a boolean is a document, and anything else a bare scalar.
-
#json_has_key(_document, _name, _path, model) ⇒ Object
key?: whether the object has the key.
-
#json_keys(_document, model) ⇒ Object
keys: the keys of an object as a JSON array.
-
#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, for which the standard likewise has nothing.
-
#json_set(_document, _steps, _dollar_path, _value, _expression, model) ⇒ Object
bury: setting a value at a path.
-
#quantifiers_supported? ⇒ Boolean
Whether a comparison can be quantified with ANY or ALL.
-
#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.
60 61 62 63 |
# File 'lib/active_record/refined/dialect.rb', line 60 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.
48 49 50 51 52 53 |
# File 'lib/active_record/refined/dialect.rb', line 48 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.
194 195 196 197 198 |
# File 'lib/active_record/refined/dialect.rb', line 194 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 |
#array_comparisons_supported? ⇒ Boolean
The array comparisons -- @>, <@ and && against an array column. The type and its operators are PostgreSQL's alone.
144 |
# File 'lib/active_record/refined/dialect.rb', line 144 def array_comparisons_supported? = false |
#bit_count(_expr, model) ⇒ Object
BIT_COUNT of a number. The standard has no equivalent; the families that do override.
153 154 155 156 |
# File 'lib/active_record/refined/dialect.rb', line 153 def bit_count(_expr, model) raise NotImplementedError, "bit_count has no equivalent on #{model.connection_db_config.adapter}" end |
#bitwise_operators_supported? ⇒ Boolean
The bitwise operators, & | ^ << >> and ~. No SQL standard has them, so unlike the capabilities above the base refuses and each family that has the operators says so itself -- which of the seven is every one but Oracle, whose single bit operation is the BITAND function.
140 |
# File 'lib/active_record/refined/dialect.rb', line 140 def bitwise_operators_supported? = false |
#bitwise_xor(left, right) ⇒ Object
XOR, which no two families spell alike. The default is the two operations it is made of, naming each operand twice -- built only from the & and | a family has just claimed through #bitwise_operators_supported?, so wherever it can be reached at all it works. Of the five that claim them, SQLite alone keeps it; the rest have an operator of their own and override.
206 207 208 209 210 |
# File 'lib/active_record/refined/dialect.rb', line 206 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.
334 |
# File 'lib/active_record/refined/dialect.rb', line 334 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.
147 |
# File 'lib/active_record/refined/dialect.rb', line 147 def check_lateral(_model); end |
#check_string_aggregate_window(_model) ⇒ Object
A family that cannot take string_agg as a window function refuses one.
351 |
# File 'lib/active_record/refined/dialect.rb', line 351 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.
181 182 183 184 185 |
# File 'lib/active_record/refined/dialect.rb', line 181 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
Whether the datetime value functions take a precision,
current_timestamp(3). SQLite and SQL Server take none.
118 |
# File 'lib/active_record/refined/dialect.rb', line 118 def datetime_precision_supported? = true |
#excluded(_column, model) ⇒ Object
The row an upsert could not insert. excluded is PostgreSQL's name
for it, which SQLite took over and the MySQL family spells
VALUES(column), so each of the three carries its own; the families
without an upsert have nothing for the name to stand in.
162 163 164 165 |
# File 'lib/active_record/refined/dialect.rb', line 162 def excluded(_column, model) raise NotImplementedError, "excluded has no equivalent on #{model.connection_db_config.adapter}" end |
#extract_supported? ⇒ Boolean
Whether the family has EXTRACT. SQLite spells the fields as strftime formats and SQL Server as DATEPART, so neither answers to the name.
122 |
# File 'lib/active_record/refined/dialect.rb', line 122 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.
134 |
# File 'lib/active_record/refined/dialect.rb', line 134 def filter_supported? = true |
#full_outer_join_supported? ⇒ Boolean
Whether a FULL OUTER JOIN can be written. The MySQL family has none.
129 |
# File 'lib/active_record/refined/dialect.rb', line 129 def full_outer_join_supported? = true |
#function_name(name, model) ⇒ String
The name this family spells a function with, asked for as the block builds the call; FUNCTIONS is where the answer is looked up.
108 109 110 111 112 113 114 |
# File 'lib/active_record/refined/dialect.rb', line 108 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.
366 |
# File 'lib/active_record/refined/dialect.rb', line 366 def grouping_by_with_rollup? = false |
#grouping_supported?(_kind) ⇒ Boolean
Whether the family has the kind of grouping asked for, one of
:grouping_sets, :rollup and :cube.
362 |
# File 'lib/active_record/refined/dialect.rb', line 362 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.
331 |
# File 'lib/active_record/refined/dialect.rb', line 331 def json_aggregate_filter_supported? = true |
#json_aggregate_name(kind) ⇒ Object
json_arrayagg / json_objectagg gather rows into a document. The names are SQL:2016's own, which the MySQL family and Oracle answer to, so unlike the rest of JSON the base keeps them; SQLite and PostgreSQL have names of their own, and SQL Server, which has no JSON aggregates, refuses.
324 325 326 |
# File 'lib/active_record/refined/dialect.rb', line 324 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. Every family marks the literal its own way -- JSON_EXTRACT($) on MySQL, json() on SQLite, FORMAT JSON on Oracle, JSON_QUERY on SQL Server -- and none of the marks is another's.
279 280 281 282 283 |
# File 'lib/active_record/refined/dialect.rb', line 279 def json_argument(_value, model) raise NotImplementedError, "a document written as JSON has no equivalent on " \ "#{model.connection_db_config.adapter}" end |
#json_build(kind, _keys, _args, model) ⇒ Object
json_array / json_object built in the row. JSON_ARRAY(a, b) is SQL:2016, but JSON_OBJECT is where the syntaxes part -- the standard pairs each key as KEY k VALUE v, MysqlishJsonFunctions alternates them, SQL Server writes k : v -- so the pair travels together and each family says its own; SQL Server's is not written here yet.
307 308 309 310 |
# File 'lib/active_record/refined/dialect.rb', line 307 def json_build(kind, _keys, _args, model) raise NotImplementedError, "json_#{kind} has no equivalent on #{model.connection_db_config.adapter}" end |
#json_build_argument(value, model) ⇒ Object
A document or boolean built into json_array/json_object. The default rides through #json_argument, and refuses or serves with it; PostgreSQL casts to jsonb and overrides.
315 316 317 |
# File 'lib/active_record/refined/dialect.rb', line 315 def json_build_argument(value, model) json_argument(value, model) end |
#json_contains(_document, _json, model) ⇒ Object
contains?: whether the document holds what is given. The standard has no equivalent; PostgreSQL has @> and the MySQL family JSON_CONTAINS, and both override.
239 240 241 242 |
# File 'lib/active_record/refined/dialect.rb', line 239 def json_contains(_document, _json, model) raise NotImplementedError, "contains? has no equivalent on #{model.connection_db_config.adapter}" end |
#json_document_value?(value) ⇒ Boolean
The test the writing hooks share: a Hash, an Array or a boolean is a document, and anything else a bare scalar.
271 272 273 |
# File 'lib/active_record/refined/dialect.rb', line 271 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
key?: whether the object has the key. SQL:2016 spells it JSON_EXISTS, which of the five only Oracle answers to; PostgreSQL has the ? operator, the MySQL family JSON_CONTAINS_PATH, SQLite json_type at the path and SQL Server JSON_PATH_EXISTS, so every family overrides.
The key arrives spelled both ways -- bare in name, as a $ path in
path -- since a family reads it as one or as the other.
252 253 254 255 |
# File 'lib/active_record/refined/dialect.rb', line 252 def json_has_key(_document, _name, _path, model) raise NotImplementedError, "key? has no equivalent on #{model.connection_db_config.adapter}" end |
#json_keys(_document, model) ⇒ Object
keys: the keys of an object as a JSON array. JSON_KEYS is the MySQL family's own; SQLite and PostgreSQL gather theirs through a subquery over their key-listing functions, and Oracle and SQL Server would reach them only through table unnests not written here.
261 262 263 264 |
# File 'lib/active_record/refined/dialect.rb', line 261 def json_keys(_document, model) raise NotImplementedError, "keys has no equivalent on #{model.connection_db_config.adapter}" 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.
355 |
# File 'lib/active_record/refined/dialect.rb', line 355 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.
230 231 232 233 234 |
# File 'lib/active_record/refined/dialect.rb', line 230 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. Nothing reaches every family: SQLite and PostgreSQL have operators of their own, MySQL its functions, and the two that spell it as SQL:2016's JSON_VALUE and JSON_QUERY -- Oracle and SQL Server -- differ over what a scalar leaf comes back as. Every family overrides.
223 224 225 226 |
# File 'lib/active_record/refined/dialect.rb', line 223 def json_path(_document, _dollar_path, _steps, _json_value, model) raise NotImplementedError, "dig has no equivalent on #{model.connection_db_config.adapter}" end |
#json_remove(_document, _dollar_paths, _steps, model) ⇒ Object
except: removing keys, for which the standard likewise has nothing. MysqlishJsonFunctions removes a path apiece with JSON_REMOVE, PostgreSQL subtracts an array of keys, Oracle and SQL Server edit through JSON_TRANSFORM and JSON_MODIFY.
297 298 299 300 |
# File 'lib/active_record/refined/dialect.rb', line 297 def json_remove(_document, _dollar_paths, _steps, model) raise NotImplementedError, "except has no equivalent on #{model.connection_db_config.adapter}" end |
#json_set(_document, _steps, _dollar_path, _value, _expression, model) ⇒ Object
bury: setting a value at a path. The standard has no editing functions at all: JSON_SET is MysqlishJsonFunctions', jsonb_set PostgreSQL's, JSON_TRANSFORM Oracle's and JSON_MODIFY SQL Server's.
288 289 290 291 |
# File 'lib/active_record/refined/dialect.rb', line 288 def json_set(_document, _steps, _dollar_path, _value, _expression, model) raise NotImplementedError, "bury has no equivalent on #{model.connection_db_config.adapter}" end |
#quantifiers_supported? ⇒ Boolean
Whether a comparison can be quantified with ANY or ALL. SQLite has neither.
126 |
# File 'lib/active_record/refined/dialect.rb', line 126 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.
344 345 346 347 348 |
# File 'lib/active_record/refined/dialect.rb', line 344 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.
170 171 172 173 |
# File 'lib/active_record/refined/dialect.rb', line 170 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 |