Class: ActiveRecord::ConnectionAdapters::CloudflareD1Adapter
- Inherits:
-
AbstractAdapter
- Object
- AbstractAdapter
- ActiveRecord::ConnectionAdapters::CloudflareD1Adapter
- Defined in:
- lib/active_record/connection_adapters/cloudflare_d1_adapter.rb
Constant Summary collapse
- ADAPTER_NAME =
"CloudflareD1"- NATIVE_DATABASE_TYPES =
{ primary_key: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL", string: { name: "TEXT" }, text: { name: "TEXT" }, integer: { name: "INTEGER" }, float: { name: "REAL" }, decimal: { name: "NUMERIC" }, datetime: { name: "TEXT" }, time: { name: "TEXT" }, date: { name: "TEXT" }, binary: { name: "BLOB" }, boolean: { name: "INTEGER" }, json: { name: "TEXT" } }.freeze
Class Method Summary collapse
Instance Method Summary collapse
-
#active? ⇒ Boolean
Connection management.
-
#adapter_name ⇒ Object
Adapter info.
- #affected_rows(result) ⇒ Object
- #columns(table_name) ⇒ Object
- #data_sources ⇒ Object
- #discard! ⇒ Object
- #disconnect! ⇒ Object
- #exec_query(sql, name = "SQL", binds = [], prepare: false) ⇒ Object
-
#execute(sql, name = nil) ⇒ Object
Query execution.
- #indexes(table_name) ⇒ Object
-
#initialize ⇒ CloudflareD1Adapter
constructor
A new instance of CloudflareD1Adapter.
- #insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) ⇒ Object
- #internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true) ⇒ Object
- #native_database_types ⇒ Object
- #perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch:) ⇒ Object
- #primary_key(table_name) ⇒ Object
- #quote_column_name(name) ⇒ Object
-
#quote_string(s) ⇒ Object
Quoting.
- #quote_table_name(name) ⇒ Object
- #quoted_date(value) ⇒ Object
- #quoted_false ⇒ Object
- #quoted_true ⇒ Object
- #reconnect ⇒ Object (also: #reconnect!)
- #reset! ⇒ Object
- #select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false) ⇒ Object
- #supports_common_table_expressions? ⇒ Boolean
- #supports_concurrent_connections? ⇒ Boolean
- #supports_datetime_with_precision? ⇒ Boolean
- #supports_ddl_transactions? ⇒ Boolean
- #supports_explain? ⇒ Boolean
- #supports_index_sort_order? ⇒ Boolean
- #supports_insert_on_conflict? ⇒ Boolean (also: #supports_insert_on_duplicate_skip?, #supports_insert_on_duplicate_update?)
- #supports_insert_returning? ⇒ Boolean
- #supports_json? ⇒ Boolean
- #supports_savepoints? ⇒ Boolean
- #supports_transaction_isolation? ⇒ Boolean
- #supports_virtual_columns? ⇒ Boolean
- #table_exists?(table_name) ⇒ Boolean
-
#tables(name = nil) ⇒ Object
Schema introspection.
-
#type_cast(value) ⇒ Object
Type casting.
- #views ⇒ Object
- #write_query?(sql) ⇒ Boolean
Constructor Details
#initialize ⇒ CloudflareD1Adapter
Returns a new instance of CloudflareD1Adapter.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 38 def initialize(...) super @config = @config.symbolize_keys @account_id = @config[:account_id] || ENV["CLOUDFLARE_ACCOUNT_ID"] @api_token = @config[:api_token] || ENV["CLOUDFLARE_API_TOKEN"] @database_id = @config[:database_id] || @config[:database] raise ArgumentError, "account_id is required" unless @account_id raise ArgumentError, "api_token is required" unless @api_token raise ArgumentError, "database_id is required" unless @database_id @client = Cloudflare::D1::Client.new( account_id: @account_id, api_token: @api_token ) @connection_parameters = @config @prepared_statements = false end |
Class Method Details
.dbconsole(config, options = {}) ⇒ Object
32 33 34 35 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 32 def dbconsole(config, = {}) puts "D1 database console not available via adapter" puts "Use: wrangler d1 execute #{config[:database_id]} --command='<SQL>'" end |
.new_client(config) ⇒ Object
28 29 30 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 28 def new_client(config) config end |
Instance Method Details
#active? ⇒ Boolean
Connection management
61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 61 def active? return false unless @client # Try a simple query to check if connection is active result = @client.query( database_id: @database_id, sql: "SELECT 1" ) result["success"] == true rescue false end |
#adapter_name ⇒ Object
Adapter info
96 97 98 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 96 def adapter_name ADAPTER_NAME end |
#affected_rows(result) ⇒ Object
194 195 196 197 198 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 194 def affected_rows(result) = result.instance_variable_get(:@meta) return 0 unless ["changes"] || 0 end |
#columns(table_name) ⇒ Object
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 267 def columns(table_name) sql = "PRAGMA table_info(#{quote_table_name(table_name)})" result = execute(sql) return [] unless result && result[0] && result[0]["results"] result[0]["results"].map do |column_data| column_name = column_data["name"] sql_type = column_data["type"] nullable = column_data["notnull"] == 0 default = column_data["dflt_value"] = (sql_type) cast_type = lookup_cast_type(sql_type) ActiveRecord::ConnectionAdapters::Column.new( column_name, cast_type, default, , nullable ) end end |
#data_sources ⇒ Object
215 216 217 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 215 def data_sources tables end |
#discard! ⇒ Object
86 87 88 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 86 def discard! @client = nil end |
#disconnect! ⇒ Object
82 83 84 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 82 def disconnect! @client = nil end |
#exec_query(sql, name = "SQL", binds = [], prepare: false) ⇒ Object
163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 163 def exec_query(sql, name = "SQL", binds = [], prepare: false) log(sql, name, binds) do result = @client.query( database_id: @database_id, sql: sql, params: binds.map { |b| type_cast(b) } ) rows = result.dig("result", 0, "results") || [] columns = rows.first&.keys || [] ActiveRecord::Result.new(columns, rows.map(&:values), result.dig("result", 0, "meta") || {}) end end |
#execute(sql, name = nil) ⇒ Object
Query execution
152 153 154 155 156 157 158 159 160 161 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 152 def execute(sql, name = nil) log(sql, name) do result = @client.query( database_id: @database_id, sql: sql ) result["result"] end end |
#indexes(table_name) ⇒ Object
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 305 def indexes(table_name) sql = "PRAGMA index_list(#{quote_table_name(table_name)})" result = execute(sql) return [] unless result && result[0] && result[0]["results"] result[0]["results"].map do |index_data| index_name = index_data["name"] unique = index_data["unique"] == 1 # Get index columns columns_sql = "PRAGMA index_info(#{quote(index_name)})" columns_result = execute(columns_sql) columns = [] if columns_result && columns_result[0] && columns_result[0]["results"] columns = columns_result[0]["results"] .sort_by { |c| c["seqno"] } .map { |c| c["name"] } end ActiveRecord::ConnectionAdapters::IndexDefinition.new( table_name, index_name, unique, columns ) end end |
#insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) ⇒ Object
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 178 def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) sql = to_sql(arel, binds) log(sql, name, binds) do result = @client.query( database_id: @database_id, sql: sql, params: binds.map { |b| type_cast(b) } ) # Return the last inserted ID wrapped in array for multi-insert support last_id = result.dig("result", 0, "meta", "last_row_id") || id_value [last_id] end end |
#internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true) ⇒ Object
223 224 225 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 223 def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true) exec_query(sql, name, binds, prepare: prepare) end |
#native_database_types ⇒ Object
301 302 303 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 301 def native_database_types NATIVE_DATABASE_TYPES end |
#perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch:) ⇒ Object
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 227 def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch:) result = @client.query( database_id: @database_id, sql: sql, params: type_casted_binds ) rows = result.dig("result", 0, "results") || [] columns = rows.first&.keys || [] = result.dig("result", 0, "meta") || {} ar_result = ActiveRecord::Result.new(columns, rows.map(&:values), ) # Store meta for affected_rows to access ar_result.instance_variable_set(:@meta, ) ar_result end |
#primary_key(table_name) ⇒ Object
291 292 293 294 295 296 297 298 299 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 291 def primary_key(table_name) sql = "PRAGMA table_info(#{quote_table_name(table_name)})" result = execute(sql) return "id" unless result && result[0] && result[0]["results"] pk_column = result[0]["results"].find { |col| col["pk"] == 1 } pk_column&.fetch("name", "id") end |
#quote_column_name(name) ⇒ Object
345 346 347 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 345 def quote_column_name(name) "\"#{name.to_s.gsub('"', '""')}\"" end |
#quote_string(s) ⇒ Object
Quoting
337 338 339 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 337 def quote_string(s) s.gsub("'", "''") end |
#quote_table_name(name) ⇒ Object
341 342 343 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 341 def quote_table_name(name) "\"#{name.to_s.gsub('"', '""')}\"" end |
#quoted_date(value) ⇒ Object
357 358 359 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 357 def quoted_date(value) value.utc.iso8601 end |
#quoted_false ⇒ Object
353 354 355 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 353 def quoted_false "0" end |
#quoted_true ⇒ Object
349 350 351 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 349 def quoted_true "1" end |
#reconnect ⇒ Object Also known as: reconnect!
74 75 76 77 78 79 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 74 def reconnect @client = Cloudflare::D1::Client.new( account_id: @account_id, api_token: @api_token ) end |
#reset! ⇒ Object
90 91 92 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 90 def reset! reconnect end |
#select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false) ⇒ Object
200 201 202 203 204 205 206 207 208 209 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 200 def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false) arel = arel_from_relation(arel) sql = to_sql(arel, binds) if preparable.nil? preparable = prepared_statements end exec_query(sql, name, binds, prepare: preparable) end |
#supports_common_table_expressions? ⇒ Boolean
128 129 130 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 128 def supports_common_table_expressions? true end |
#supports_concurrent_connections? ⇒ Boolean
146 147 148 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 146 def supports_concurrent_connections? true end |
#supports_datetime_with_precision? ⇒ Boolean
120 121 122 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 120 def supports_datetime_with_precision? false end |
#supports_ddl_transactions? ⇒ Boolean
100 101 102 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 100 def supports_ddl_transactions? false # D1 API doesn't support transactions across HTTP requests end |
#supports_explain? ⇒ Boolean
112 113 114 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 112 def supports_explain? true end |
#supports_index_sort_order? ⇒ Boolean
116 117 118 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 116 def supports_index_sort_order? true end |
#supports_insert_on_conflict? ⇒ Boolean Also known as: supports_insert_on_duplicate_skip?, supports_insert_on_duplicate_update?
140 141 142 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 140 def supports_insert_on_conflict? true end |
#supports_insert_returning? ⇒ Boolean
136 137 138 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 136 def supports_insert_returning? false end |
#supports_json? ⇒ Boolean
124 125 126 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 124 def supports_json? true end |
#supports_savepoints? ⇒ Boolean
104 105 106 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 104 def supports_savepoints? false end |
#supports_transaction_isolation? ⇒ Boolean
108 109 110 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 108 def supports_transaction_isolation? false end |
#supports_virtual_columns? ⇒ Boolean
132 133 134 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 132 def supports_virtual_columns? false end |
#table_exists?(table_name) ⇒ Boolean
263 264 265 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 263 def table_exists?(table_name) tables.include?(table_name.to_s) end |
#tables(name = nil) ⇒ Object
Schema introspection
248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 248 def tables(name = nil) sql = " SELECT name FROM sqlite_master\n WHERE type = 'table'\n AND name NOT LIKE 'sqlite_%'\n AND name NOT IN ('ar_internal_metadata')\n ORDER BY name\n SQL\n\n result = execute(sql, name)\n return [] unless result && result[0] && result[0][\"results\"]\n\n result[0][\"results\"].map { |row| row[\"name\"] }\nend\n" |
#type_cast(value) ⇒ Object
Type casting
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 363 def type_cast(value) case value when ActiveModel::Attribute type_cast(value.value_for_database) when TrueClass 1 when FalseClass 0 when Time, DateTime value.utc.iso8601 when Date value.iso8601 else value end end |
#views ⇒ Object
211 212 213 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 211 def views [] end |
#write_query?(sql) ⇒ Boolean
219 220 221 |
# File 'lib/active_record/connection_adapters/cloudflare_d1_adapter.rb', line 219 def write_query?(sql) !sql.match?(/\A\s*(SELECT|PRAGMA|EXPLAIN)/i) end |