Module: JadeSql::Runtime
- Extended by:
- Jade::Port
- Defined in:
- lib/jade-sql/runtime.rb
Constant Summary collapse
- PG_ARRAY_LITERAL =
PG arrays render as ‘{}`, `a,b,c`, `“a,b”,“c”`, with NULL as bare `NULL`. Quoted elements escape `“` and `` with backslashes. The JSON-object guard rejects `”key“:…` shapes — they share the outer braces but should reach Decode.Value as plain strings (or as Hash if AR already typecast the column).
/\A\{.*\}\z/m- JSON_OBJECT_HEAD =
/\A\{\s*"[^"]*"\s*:/m- QUOTED_OR_PLACEHOLDER =
Sql renders ‘?` placeholders uniformly. AR’s exec_query/exec_update path on the PG adapter expects ‘$1, $2, …` — there is no `?`-to-`$n` rewrite at that layer. SQLite and MySQL accept `?` directly, so this is a no-op there.
The alternation matches a whole quoted span first (single-quoted string with ‘”` escapes, or double-quoted identifier with `“”` escapes), so a literal `?` inside one is left alone — only bare `?` outside quotes becomes a placeholder. Dollar-quoted bodies aren’t handled (uncommon in app SQL).
/'(?:[^']|'')*'|"(?:[^"]|"")*"|\?/
Class Method Summary collapse
- .adapt_sql(sql, conn) ⇒ Object
- .array_type_for(elements) ⇒ Object
-
.coerce_row(row) ⇒ Object
AR’s PG adapter returns ::Date / ::Time for date/timestamp columns; Calendar.Date / Clock.Instant decoders expect ISO strings.
- .coerce_value(v) ⇒ Object
- .decode_element(raw) ⇒ Object
- .parse_pg_array(s) ⇒ Object
- .pg_array_literal?(s) ⇒ Boolean
- .typed_param(value) ⇒ Object
-
.typed_params(params, conn) ⇒ Object
AR’s exec_query raw path can’t bind a Ruby Array — pg’s OID type cast isn’t applied to bare values.
Class Method Details
.adapt_sql(sql, conn) ⇒ Object
154 155 156 157 158 159 |
# File 'lib/jade-sql/runtime.rb', line 154 def self.adapt_sql(sql, conn) return sql unless conn.adapter_name =~ /postgres/i n = 0 sql.gsub(QUOTED_OR_PLACEHOLDER) { |m| m == "?" ? "$#{n += 1}" : m } end |
.array_type_for(elements) ⇒ Object
180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/jade-sql/runtime.rb', line 180 def self.array_type_for(elements) sample = elements.find { |e| !e.nil? } element_type = case sample when ::Integer then ::ActiveRecord::Type::Integer.new when ::Float then ::ActiveRecord::Type::Float.new when true, false then ::ActiveRecord::Type::Boolean.new else ::ActiveRecord::Type::String.new end ::ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(element_type) end |
.coerce_row(row) ⇒ Object
AR’s PG adapter returns ::Date / ::Time for date/timestamp columns; Calendar.Date / Clock.Instant decoders expect ISO strings. Coerce at the boundary so callers don’t sprinkle text_cast in every SELECT.
text[] / int[] / etc. arrive as Postgres array literals (‘a,b,c`) when AR’s exec_query path doesn’t run the OID typecast. Parse them back to Ruby Arrays so ‘Decode.list(…)` works the same as for any other List(a) column.
76 77 78 |
# File 'lib/jade-sql/runtime.rb', line 76 def self.coerce_row(row) row.transform_values { |v| coerce_value(v) } end |
.coerce_value(v) ⇒ Object
80 81 82 83 84 85 86 87 88 |
# File 'lib/jade-sql/runtime.rb', line 80 def self.coerce_value(v) case v when ::Date then v.iso8601 when ::Time, ::DateTime then v.iso8601 when ::String pg_array_literal?(v) ? parse_pg_array(v) : v else v end end |
.decode_element(raw) ⇒ Object
138 139 140 |
# File 'lib/jade-sql/runtime.rb', line 138 def self.decode_element(raw) raw == "NULL" ? nil : raw end |
.parse_pg_array(s) ⇒ Object
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/jade-sql/runtime.rb', line 102 def self.parse_pg_array(s) inner = s[1..-2] return [] if inner.empty? elements = [] buffer = String.new in_quotes = false i = 0 while i < inner.length c = inner[i] if in_quotes if c == '\\' && i + 1 < inner.length buffer << inner[i + 1] i += 2 next elsif c == '"' in_quotes = false else buffer << c end else if c == '"' in_quotes = true elsif c == ',' elements << decode_element(buffer) buffer = String.new else buffer << c end end i += 1 end elements << decode_element(buffer) elements end |
.pg_array_literal?(s) ⇒ Boolean
98 99 100 |
# File 'lib/jade-sql/runtime.rb', line 98 def self.pg_array_literal?(s) s.match?(PG_ARRAY_LITERAL) && !s.match?(JSON_OBJECT_HEAD) end |
.typed_param(value) ⇒ Object
171 172 173 174 175 176 177 178 |
# File 'lib/jade-sql/runtime.rb', line 171 def self.typed_param(value) case value when ::Array ::ActiveRecord::Relation::QueryAttribute.new(nil, value, array_type_for(value)) else value end end |
.typed_params(params, conn) ⇒ Object
AR’s exec_query raw path can’t bind a Ruby Array — pg’s OID type cast isn’t applied to bare values. Wrap arrays in QueryAttribute with a PG OID::Array so the binding picks the right wire format. Element type sniffs the first non-nil entry; falls back to text.
165 166 167 168 169 |
# File 'lib/jade-sql/runtime.rb', line 165 def self.typed_params(params, conn) return params unless conn.adapter_name =~ /postgres/i params.map { |p| typed_param(p) } end |