rubydb-activerecord 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +8 -3
- data/lib/active_record/connection_adapters/rubydb_adapter.rb +881 -879
- data/rubydb-activerecord.gemspec +21 -21
- metadata +1 -1
|
@@ -1,766 +1,772 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "active_record"
|
|
4
|
-
require "active_record/connection_adapters/abstract_adapter"
|
|
5
|
-
require "active_record/connection_adapters/abstract/schema_definitions"
|
|
6
|
-
require "active_record/connection_adapters/abstract/schema_statements"
|
|
7
|
-
|
|
8
|
-
require "rubydb"
|
|
9
|
-
require "rubydb/rails/adapter"
|
|
10
|
-
require "rubydb/rails/connection"
|
|
11
|
-
require "rubydb/rails/database_statements"
|
|
12
|
-
require "rubydb/rails/schema_statements"
|
|
13
|
-
require "rubydb/rails/quoting"
|
|
14
|
-
require "rubydb/rails/type"
|
|
15
|
-
require "rubydb/rails/result"
|
|
16
|
-
|
|
17
|
-
module ActiveRecord
|
|
18
|
-
module ConnectionAdapters
|
|
19
|
-
# RubyDB adapter for ActiveRecord
|
|
20
|
-
class RubyDBAdapter < AbstractAdapter
|
|
21
|
-
include RubyDB::Rails::DatabaseStatements
|
|
22
|
-
include RubyDB::Rails::SchemaStatements
|
|
23
|
-
include RubyDB::Rails::Quoting
|
|
24
|
-
|
|
25
|
-
ADAPTER_NAME = "RubyDB"
|
|
26
|
-
|
|
27
|
-
# ActiveRecord 7.2 uses adapter-level methods while compiling hash-form
|
|
28
|
-
# order clauses (for example, `order(created_at: :desc)`). Keep these
|
|
29
|
-
# independent of a live connection so relation construction is safe
|
|
30
|
-
# during schema-cache and query setup as well.
|
|
31
|
-
def self.quote_table_name(name)
|
|
32
|
-
quote_column_name(name)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def self.quote_column_name(name)
|
|
36
|
-
"\"#{name.to_s.gsub('"', '""')}\""
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
NATIVE_DATABASE_TYPES = {
|
|
40
|
-
primary_key: "INTEGER PRIMARY KEY AUTOINCREMENT",
|
|
41
|
-
string: {
|
|
42
|
-
text: {
|
|
43
|
-
integer: {
|
|
44
|
-
bigint: {
|
|
45
|
-
smallint: {
|
|
46
|
-
float: {
|
|
47
|
-
decimal: {
|
|
48
|
-
datetime: {
|
|
49
|
-
timestamp: {
|
|
50
|
-
time: {
|
|
51
|
-
date: {
|
|
52
|
-
binary: {
|
|
53
|
-
boolean: {
|
|
54
|
-
json: {
|
|
55
|
-
uuid: {
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
# ActiveRecord 7.2 constructs adapters with a single configuration hash.
|
|
59
|
-
# Accept trailing deprecated arguments so applications upgrading from older
|
|
60
|
-
# ActiveRecord versions do not fail during connection establishment.
|
|
61
|
-
def initialize(config, *)
|
|
62
|
-
super(config)
|
|
63
|
-
|
|
64
|
-
@connection = RubyDB::Rails::Connection.new(config)
|
|
65
|
-
@connection.connect
|
|
66
|
-
|
|
67
|
-
@prepared_statements = {}
|
|
68
|
-
@transaction_depth = 0
|
|
69
|
-
@query_cache_enabled = false
|
|
70
|
-
@query_cache = {}
|
|
71
|
-
@statements = {}
|
|
72
|
-
@statement_counter = 0
|
|
73
|
-
# AbstractAdapter uses this monitor while creating transactions. It
|
|
74
|
-
# must be re-entrant because ActiveRecord acquires it recursively.
|
|
75
|
-
@lock = Monitor.new
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def adapter_name
|
|
79
|
-
ADAPTER_NAME
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def supports_migrations?
|
|
83
|
-
true
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def supports_primary_key?
|
|
87
|
-
true
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def supports_index_sort_order?
|
|
91
|
-
true
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def supports_transactions?
|
|
95
|
-
true
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def supports_savepoints?
|
|
99
|
-
true
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def supports_foreign_keys?
|
|
103
|
-
true
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def supports_views?
|
|
107
|
-
true
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def supports_json?
|
|
111
|
-
true
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def supports_uuid?
|
|
115
|
-
true
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def supports_bulk_alter?
|
|
119
|
-
false
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def native_database_types
|
|
123
|
-
NATIVE_DATABASE_TYPES
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
# ==================== SCHEMA METHODS ====================
|
|
127
|
-
|
|
128
|
-
def primary_key(table_name)
|
|
129
|
-
return @connection.engine.table_columns(table_name).find(&:primary_key?)&.name&.to_s || "id" if embedded?
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def tables
|
|
137
|
-
return @connection.engine.list_tables.map(&:to_s) if embedded?
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
params = bind_values(binds)
|
|
251
|
-
result = @connection.execute(sql, params)
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record"
|
|
4
|
+
require "active_record/connection_adapters/abstract_adapter"
|
|
5
|
+
require "active_record/connection_adapters/abstract/schema_definitions"
|
|
6
|
+
require "active_record/connection_adapters/abstract/schema_statements"
|
|
7
|
+
|
|
8
|
+
require "rubydb"
|
|
9
|
+
require "rubydb/rails/adapter"
|
|
10
|
+
require "rubydb/rails/connection"
|
|
11
|
+
require "rubydb/rails/database_statements"
|
|
12
|
+
require "rubydb/rails/schema_statements"
|
|
13
|
+
require "rubydb/rails/quoting"
|
|
14
|
+
require "rubydb/rails/type"
|
|
15
|
+
require "rubydb/rails/result"
|
|
16
|
+
|
|
17
|
+
module ActiveRecord
|
|
18
|
+
module ConnectionAdapters
|
|
19
|
+
# RubyDB adapter for ActiveRecord
|
|
20
|
+
class RubyDBAdapter < AbstractAdapter
|
|
21
|
+
include RubyDB::Rails::DatabaseStatements
|
|
22
|
+
include RubyDB::Rails::SchemaStatements
|
|
23
|
+
include RubyDB::Rails::Quoting
|
|
24
|
+
|
|
25
|
+
ADAPTER_NAME = "RubyDB"
|
|
26
|
+
|
|
27
|
+
# ActiveRecord 7.2 uses adapter-level methods while compiling hash-form
|
|
28
|
+
# order clauses (for example, `order(created_at: :desc)`). Keep these
|
|
29
|
+
# independent of a live connection so relation construction is safe
|
|
30
|
+
# during schema-cache and query setup as well.
|
|
31
|
+
def self.quote_table_name(name)
|
|
32
|
+
quote_column_name(name)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.quote_column_name(name)
|
|
36
|
+
"\"#{name.to_s.gsub('"', '""')}\""
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
NATIVE_DATABASE_TYPES = {
|
|
40
|
+
primary_key: "INTEGER PRIMARY KEY AUTOINCREMENT",
|
|
41
|
+
string: {name: "VARCHAR", limit: 255},
|
|
42
|
+
text: {name: "TEXT"},
|
|
43
|
+
integer: {name: "INTEGER"},
|
|
44
|
+
bigint: {name: "BIGINT"},
|
|
45
|
+
smallint: {name: "SMALLINT"},
|
|
46
|
+
float: {name: "FLOAT"},
|
|
47
|
+
decimal: {name: "DECIMAL", precision: 10, scale: 2},
|
|
48
|
+
datetime: {name: "TIMESTAMP"},
|
|
49
|
+
timestamp: {name: "TIMESTAMP"},
|
|
50
|
+
time: {name: "TIME"},
|
|
51
|
+
date: {name: "DATE"},
|
|
52
|
+
binary: {name: "BLOB"},
|
|
53
|
+
boolean: {name: "BOOLEAN"},
|
|
54
|
+
json: {name: "JSON"},
|
|
55
|
+
uuid: {name: "UUID"}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# ActiveRecord 7.2 constructs adapters with a single configuration hash.
|
|
59
|
+
# Accept trailing deprecated arguments so applications upgrading from older
|
|
60
|
+
# ActiveRecord versions do not fail during connection establishment.
|
|
61
|
+
def initialize(config, *)
|
|
62
|
+
super(config)
|
|
63
|
+
|
|
64
|
+
@connection = RubyDB::Rails::Connection.new(config)
|
|
65
|
+
@connection.connect
|
|
66
|
+
|
|
67
|
+
@prepared_statements = {}
|
|
68
|
+
@transaction_depth = 0
|
|
69
|
+
@query_cache_enabled = false
|
|
70
|
+
@query_cache = {}
|
|
71
|
+
@statements = {}
|
|
72
|
+
@statement_counter = 0
|
|
73
|
+
# AbstractAdapter uses this monitor while creating transactions. It
|
|
74
|
+
# must be re-entrant because ActiveRecord acquires it recursively.
|
|
75
|
+
@lock = Monitor.new
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def adapter_name
|
|
79
|
+
ADAPTER_NAME
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def supports_migrations?
|
|
83
|
+
true
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def supports_primary_key?
|
|
87
|
+
true
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def supports_index_sort_order?
|
|
91
|
+
true
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def supports_transactions?
|
|
95
|
+
true
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def supports_savepoints?
|
|
99
|
+
true
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def supports_foreign_keys?
|
|
103
|
+
true
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def supports_views?
|
|
107
|
+
true
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def supports_json?
|
|
111
|
+
true
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def supports_uuid?
|
|
115
|
+
true
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def supports_bulk_alter?
|
|
119
|
+
false
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def native_database_types
|
|
123
|
+
NATIVE_DATABASE_TYPES
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# ==================== SCHEMA METHODS ====================
|
|
127
|
+
|
|
128
|
+
def primary_key(table_name)
|
|
129
|
+
return @connection.engine.table_columns(table_name).find(&:primary_key?)&.name&.to_s || "id" if embedded?
|
|
130
|
+
|
|
131
|
+
remote_metadata(table_name)[:columns].find do |column|
|
|
132
|
+
column[:primary_key] || column["primary_key"]
|
|
133
|
+
end&.then { |column| (column[:name] || column["name"]).to_s } || "id"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def tables
|
|
137
|
+
return @connection.engine.list_tables.map(&:to_s) if embedded?
|
|
138
|
+
|
|
139
|
+
Array(remote_metadata[:tables]).map(&:to_s)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def table_exists?(table_name)
|
|
143
|
+
tables.include?(table_name.to_s)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# ActiveRecord's schema cache asks for these before a model is first
|
|
147
|
+
# instantiated. The embedded engine has catalog metadata already, so
|
|
148
|
+
# avoid generating unsupported SQLite catalog queries.
|
|
149
|
+
def data_sources
|
|
150
|
+
tables
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def data_source_exists?(name)
|
|
154
|
+
table_exists?(name)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def views
|
|
158
|
+
[]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def indexes(table_name)
|
|
162
|
+
if embedded?
|
|
163
|
+
return @connection.engine.index_manager.get_indexes_for_table(table_name.to_s).map do |index|
|
|
164
|
+
ActiveRecord::ConnectionAdapters::IndexDefinition.new(
|
|
165
|
+
table_name.to_s,
|
|
166
|
+
index.name.to_s,
|
|
167
|
+
index.unique,
|
|
168
|
+
index.columns.map(&:to_s)
|
|
169
|
+
)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
Array(remote_metadata(table_name)[:indexes]).map do |index|
|
|
174
|
+
ActiveRecord::ConnectionAdapters::IndexDefinition.new(
|
|
175
|
+
table_name.to_s,
|
|
176
|
+
index[:name] || index["name"],
|
|
177
|
+
index[:unique] || index["unique"],
|
|
178
|
+
index[:columns] || index["columns"] || []
|
|
179
|
+
)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def columns(table_name)
|
|
184
|
+
return embedded_columns(table_name) if embedded?
|
|
185
|
+
|
|
186
|
+
remote_columns(table_name)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def remote_columns(table_name)
|
|
190
|
+
Array(remote_metadata(table_name)[:columns]).map do |row|
|
|
191
|
+
type = row[:type] || row["type"]
|
|
192
|
+
nullable = row.key?(:nullable) ? row[:nullable] : row["nullable"]
|
|
193
|
+
ActiveRecord::ConnectionAdapters::Column.new(
|
|
194
|
+
row[:name] || row["name"],
|
|
195
|
+
rails_default_value(row.key?(:default) ? row[:default] : row["default"]),
|
|
196
|
+
ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(
|
|
197
|
+
sql_type: type.to_s.upcase,
|
|
198
|
+
type: rails_type_for(type),
|
|
199
|
+
limit: extract_limit(type.to_s)
|
|
200
|
+
),
|
|
201
|
+
nullable
|
|
202
|
+
)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def column_exists?(table_name, column_name)
|
|
207
|
+
columns(table_name).any? { |c| c.name == column_name }
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# ==================== QUERY METHODS ====================
|
|
211
|
+
|
|
212
|
+
def execute(sql, name = nil)
|
|
213
|
+
sql = sql_for_execution(sql)
|
|
214
|
+
log(sql, name) do
|
|
215
|
+
@connection.execute(sql)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def exec_query(sql, name = nil, binds = [])
|
|
220
|
+
sql = sql_for_execution(sql)
|
|
221
|
+
log(sql, name) do
|
|
222
|
+
params = bind_values(binds)
|
|
223
|
+
active_record_result(@connection.execute(sql, params))
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def exec_delete(sql, name = nil, binds = [])
|
|
228
|
+
sql = sql_for_execution(sql)
|
|
229
|
+
log(sql, name) do
|
|
230
|
+
params = bind_values(binds)
|
|
231
|
+
result = @connection.execute(sql, params)
|
|
232
|
+
result.affected_rows
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def exec_update(sql, name = nil, binds = [])
|
|
237
|
+
sql = sql_for_execution(sql)
|
|
238
|
+
log(sql, name) do
|
|
239
|
+
params = bind_values(binds)
|
|
240
|
+
result = @connection.execute(sql, params)
|
|
241
|
+
result.affected_rows
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil)
|
|
246
|
+
sql = sql_for_execution(sql)
|
|
247
|
+
log(sql, name) do
|
|
248
|
+
params = bind_values(binds)
|
|
249
|
+
result = @connection.execute(sql, params)
|
|
252
250
|
# RubyDB keeps a physical row id for storage operations and a
|
|
253
251
|
# logical primary-key value for SQL/ActiveRecord. They can differ
|
|
254
252
|
# after deletes, so ActiveRecord must receive the logical id.
|
|
255
253
|
id = result.inserted_id || result.row_id
|
|
256
|
-
ActiveRecord::Result.new([pk || "id"], id.nil? ? [] : [[id]])
|
|
257
|
-
end
|
|
258
|
-
end
|
|
259
|
-
|
|
260
|
-
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [], returning: nil)
|
|
261
|
-
sql, binds = to_sql_and_binds(arel, binds)
|
|
262
|
-
result = exec_insert(sql, name, binds, pk, sequence_name, returning: returning)
|
|
263
|
-
return returning_column_values(result) unless returning.nil?
|
|
264
|
-
|
|
265
|
-
id_value || last_inserted_id(result)
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
def update(arel, name = nil, binds = [])
|
|
269
|
-
sql, binds = to_sql_and_binds(arel, binds)
|
|
270
|
-
exec_update(sql, name, binds)
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
def delete(arel, name = nil, binds = [])
|
|
274
|
-
sql, binds = to_sql_and_binds(arel, binds)
|
|
275
|
-
exec_delete(sql, name, binds)
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
def select_all(sql, name = nil, binds = [], preparable: nil, async: false, allow_retry: false)
|
|
279
|
-
sql, binds = to_sql_and_binds(sql, binds)
|
|
280
|
-
exec_query(sql, name, binds)
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
def select_one(sql, name = nil, binds = [])
|
|
284
|
-
sql, binds = to_sql_and_binds(sql, binds)
|
|
285
|
-
result = exec_query(sql, name, binds)
|
|
286
|
-
result.first
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
def select_value(sql, name = nil, binds = [])
|
|
290
|
-
sql, binds = to_sql_and_binds(sql, binds)
|
|
291
|
-
result = exec_query(sql, name, binds)
|
|
292
|
-
result.first&.values&.first
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
def select_values(sql, name = nil, binds = [])
|
|
296
|
-
sql, binds = to_sql_and_binds(sql, binds)
|
|
297
|
-
result = exec_query(sql, name, binds)
|
|
298
|
-
result.map { |row| row.values.first }
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
def select_rows(sql, name = nil, binds = [])
|
|
302
|
-
sql, binds = to_sql_and_binds(sql, binds)
|
|
303
|
-
result = exec_query(sql, name, binds)
|
|
304
|
-
result.map { |row| row.values }
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
# ==================== TRANSACTION METHODS ====================
|
|
308
|
-
|
|
309
|
-
def begin_db_transaction
|
|
310
|
-
@transaction_depth += 1
|
|
311
|
-
@connection.begin_db_transaction if @transaction_depth == 1
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
def commit_db_transaction
|
|
315
|
-
return if @transaction_depth <= 0
|
|
316
|
-
|
|
317
|
-
@transaction_depth -= 1
|
|
318
|
-
@connection.commit_db_transaction if @transaction_depth == 0
|
|
319
|
-
end
|
|
320
|
-
|
|
321
|
-
def rollback_db_transaction
|
|
322
|
-
return if @transaction_depth <= 0
|
|
323
|
-
|
|
324
|
-
@transaction_depth -= 1
|
|
325
|
-
@connection.rollback_db_transaction if @transaction_depth == 0
|
|
326
|
-
@transaction_depth = 0 if @transaction_depth < 0
|
|
327
|
-
end
|
|
328
|
-
|
|
329
|
-
def create_savepoint(name)
|
|
330
|
-
execute("SAVEPOINT #{name}")
|
|
331
|
-
end
|
|
332
|
-
|
|
333
|
-
def rollback_to_savepoint(name)
|
|
334
|
-
execute("ROLLBACK TO SAVEPOINT #{name}")
|
|
335
|
-
end
|
|
336
|
-
|
|
337
|
-
def release_savepoint(name)
|
|
338
|
-
execute("RELEASE SAVEPOINT #{name}")
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
def in_transaction?
|
|
342
|
-
@transaction_depth > 0
|
|
343
|
-
end
|
|
344
|
-
|
|
345
|
-
def transaction_joinable?
|
|
346
|
-
true
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
-
def transactional?
|
|
350
|
-
true
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
# ==================== SCHEMA STATEMENT METHODS ====================
|
|
354
|
-
|
|
355
|
-
def create_table(table_name, **options, &
|
|
356
|
-
RubyDB::Rails::SchemaStatements.instance_method(:create_table).bind_call(self, table_name, options, &
|
|
357
|
-
end
|
|
358
|
-
|
|
359
|
-
def drop_table(table_name, **options)
|
|
360
|
-
sql = +"DROP TABLE"
|
|
361
|
-
sql << " IF EXISTS" if options[:if_exists]
|
|
362
|
-
sql << " #{quote_table_name(table_name)}"
|
|
363
|
-
sql << " CASCADE" if options[:cascade]
|
|
364
|
-
execute(sql)
|
|
365
|
-
end
|
|
366
|
-
|
|
367
|
-
def add_column(table_name, column_name, type, **options)
|
|
368
|
-
sql = "ALTER TABLE #{quote_table_name(table_name)}"
|
|
369
|
-
sql << " ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options)}"
|
|
370
|
-
|
|
371
|
-
if options[:null] == false
|
|
372
|
-
sql << " NOT NULL"
|
|
373
|
-
end
|
|
374
|
-
|
|
375
|
-
if options[:default]
|
|
376
|
-
sql << " DEFAULT #{quote_default(options[:default])}"
|
|
377
|
-
end
|
|
378
|
-
|
|
379
|
-
if options[:primary_key]
|
|
380
|
-
sql << " PRIMARY KEY"
|
|
381
|
-
end
|
|
382
|
-
|
|
383
|
-
execute(sql)
|
|
384
|
-
end
|
|
385
|
-
|
|
386
|
-
def remove_column(table_name, column_name, type = nil, **options)
|
|
387
|
-
sql = "ALTER TABLE #{quote_table_name(table_name)}"
|
|
388
|
-
sql << " DROP COLUMN #{quote_column_name(column_name)}"
|
|
389
|
-
sql << " CASCADE" if options[:cascade]
|
|
390
|
-
execute(sql)
|
|
391
|
-
end
|
|
392
|
-
|
|
393
|
-
def change_column(table_name, column_name, type, **options)
|
|
394
|
-
# Change column type
|
|
395
|
-
sql = "ALTER TABLE #{quote_table_name(table_name)}"
|
|
396
|
-
sql << " ALTER COLUMN #{quote_column_name(column_name)}"
|
|
397
|
-
sql << " TYPE #{type_to_sql(type, options)}"
|
|
398
|
-
execute(sql)
|
|
399
|
-
|
|
400
|
-
# Change nullability
|
|
401
|
-
if options.key?(:null)
|
|
402
|
-
sql = "ALTER TABLE #{quote_table_name(table_name)}"
|
|
403
|
-
sql << " ALTER COLUMN #{quote_column_name(column_name)}"
|
|
404
|
-
sql << (options[:null] ? " DROP" : " SET") + " NOT NULL"
|
|
405
|
-
execute(sql)
|
|
406
|
-
end
|
|
407
|
-
|
|
408
|
-
# Change default
|
|
409
|
-
if options.key?(:default)
|
|
410
|
-
sql = "ALTER TABLE #{quote_table_name(table_name)}"
|
|
411
|
-
sql << " ALTER COLUMN #{quote_column_name(column_name)}"
|
|
412
|
-
if options[:default].nil?
|
|
413
|
-
|
|
414
|
-
else
|
|
415
|
-
|
|
416
|
-
end
|
|
417
|
-
execute(sql)
|
|
418
|
-
end
|
|
419
|
-
end
|
|
420
|
-
|
|
421
|
-
def rename_column(table_name, column_name, new_column_name)
|
|
422
|
-
sql = "ALTER TABLE #{quote_table_name(table_name)}"
|
|
423
|
-
sql << " RENAME COLUMN #{quote_column_name(column_name)}"
|
|
424
|
-
sql << " TO #{quote_column_name(new_column_name)}"
|
|
425
|
-
execute(sql)
|
|
426
|
-
end
|
|
427
|
-
|
|
428
|
-
def rename_table(old_name, new_name)
|
|
429
|
-
sql = "ALTER TABLE #{quote_table_name(old_name)}"
|
|
430
|
-
sql << " RENAME TO #{quote_table_name(new_name)}"
|
|
431
|
-
execute(sql)
|
|
432
|
-
end
|
|
433
|
-
|
|
434
|
-
def add_index(table_name, column_name, **options)
|
|
435
|
-
index_name = options[:name] || "idx_#{table_name}_#{Array(column_name).join(
|
|
436
|
-
sql = +"CREATE"
|
|
437
|
-
sql << " UNIQUE" if options[:unique]
|
|
438
|
-
sql << " INDEX #{quote_column_name(index_name)}"
|
|
439
|
-
sql << " ON #{quote_table_name(table_name)}"
|
|
440
|
-
sql << " (#{Array(column_name).map { |c| quote_column_name(c) }.join(
|
|
441
|
-
sql << " WHERE #{options[:where]}" if options[:where]
|
|
442
|
-
execute(sql)
|
|
443
|
-
end
|
|
444
|
-
|
|
445
|
-
def remove_index(table_name, column_name = nil, **options)
|
|
446
|
-
index_name = options[:name]
|
|
447
|
-
if index_name.nil?
|
|
448
|
-
column_name ||= options[:column] || options[:columns]
|
|
449
|
-
index_name = "idx_#{table_name}_#{Array(column_name).join(
|
|
450
|
-
end
|
|
451
|
-
|
|
452
|
-
sql = "DROP INDEX #{quote_column_name(index_name)}"
|
|
453
|
-
execute(sql)
|
|
454
|
-
end
|
|
455
|
-
|
|
456
|
-
def add_foreign_key(from_table, to_table, **options)
|
|
457
|
-
fk_name = options[:name] || "fk_#{from_table}_to_#{to_table}"
|
|
458
|
-
sql = "ALTER TABLE #{quote_table_name(from_table)}"
|
|
459
|
-
sql << " ADD CONSTRAINT #{quote_column_name(fk_name)}"
|
|
460
|
-
sql << " FOREIGN KEY (#{quote_column_name(options[:column] || :id)})"
|
|
461
|
-
sql << " REFERENCES #{quote_table_name(to_table)}"
|
|
462
|
-
sql << " (#{quote_column_name(options[:primary_key] || :id)})"
|
|
463
|
-
sql << " ON DELETE #{options[:on_delete]}" if options[:on_delete]
|
|
464
|
-
sql << " ON UPDATE #{options[:on_update]}" if options[:on_update]
|
|
465
|
-
execute(sql)
|
|
466
|
-
end
|
|
467
|
-
|
|
468
|
-
def foreign_keys(table_name)
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
sql
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
sql
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
schema << "
|
|
592
|
-
schema << ",
|
|
593
|
-
schema << "
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
schema << ",
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
when
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
@
|
|
626
|
-
@
|
|
627
|
-
@
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
# ==================== PREPARED STATEMENTS ====================
|
|
667
|
-
|
|
668
|
-
def prepare_statement(sql)
|
|
669
|
-
@lock.synchronize do
|
|
670
|
-
stmt_id = "stmt_#{Time.now.to_i}_#{@statement_counter}"
|
|
671
|
-
@statement_counter += 1
|
|
672
|
-
|
|
673
|
-
result = @connection.prepare(sql)
|
|
674
|
-
@prepared_statements[stmt_id] = {
|
|
675
|
-
id: result.statement_id,
|
|
676
|
-
sql: sql,
|
|
677
|
-
created_at: Time.now
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
stmt_id
|
|
681
|
-
end
|
|
682
|
-
end
|
|
683
|
-
|
|
684
|
-
def execute_prepared_statement(stmt_id, params = [])
|
|
685
|
-
@lock.synchronize do
|
|
686
|
-
stmt = @prepared_statements[stmt_id]
|
|
687
|
-
return nil unless stmt
|
|
688
|
-
|
|
689
|
-
@connection.execute_prepared(stmt[:id], params)
|
|
690
|
-
end
|
|
691
|
-
end
|
|
692
|
-
|
|
693
|
-
def close_statement(stmt_id)
|
|
694
|
-
@lock.synchronize do
|
|
695
|
-
stmt = @prepared_statements.delete(stmt_id)
|
|
696
|
-
if stmt
|
|
697
|
-
@connection.close_statement(stmt[:id])
|
|
698
|
-
end
|
|
699
|
-
end
|
|
700
|
-
end
|
|
701
|
-
|
|
702
|
-
# ==================== VERSION INFORMATION ====================
|
|
703
|
-
|
|
704
|
-
def dbms_version
|
|
705
|
-
RubyDB::VERSION
|
|
706
|
-
end
|
|
707
|
-
|
|
708
|
-
# ==================== FEATURE SUPPORT ====================
|
|
709
|
-
|
|
710
|
-
def supports_datetime_with_precision?
|
|
711
|
-
true
|
|
712
|
-
end
|
|
713
|
-
|
|
714
|
-
def supports_materialized_views?
|
|
715
|
-
false
|
|
716
|
-
end
|
|
717
|
-
|
|
718
|
-
def supports_common_table_expressions?
|
|
719
|
-
false
|
|
720
|
-
end
|
|
721
|
-
|
|
722
|
-
# ==================== PRIVATE METHODS ====================
|
|
723
|
-
|
|
724
|
-
private
|
|
725
|
-
|
|
726
|
-
def embedded?
|
|
727
|
-
!@connection.engine.nil?
|
|
728
|
-
end
|
|
729
|
-
|
|
730
|
-
def
|
|
731
|
-
@connection.
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
column.
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
when :
|
|
754
|
-
when :
|
|
755
|
-
when :
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
254
|
+
ActiveRecord::Result.new([pk || "id"], id.nil? ? [] : [[id]])
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [], returning: nil)
|
|
259
|
+
sql, binds = to_sql_and_binds(arel, binds)
|
|
260
|
+
result = exec_insert(sql, name, binds, pk, sequence_name, returning: returning)
|
|
261
|
+
return returning_column_values(result) unless returning.nil?
|
|
262
|
+
|
|
263
|
+
id_value || last_inserted_id(result)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def update(arel, name = nil, binds = [])
|
|
267
|
+
sql, binds = to_sql_and_binds(arel, binds)
|
|
268
|
+
exec_update(sql, name, binds)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def delete(arel, name = nil, binds = [])
|
|
272
|
+
sql, binds = to_sql_and_binds(arel, binds)
|
|
273
|
+
exec_delete(sql, name, binds)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def select_all(sql, name = nil, binds = [], preparable: nil, async: false, allow_retry: false)
|
|
277
|
+
sql, binds = to_sql_and_binds(sql, binds)
|
|
278
|
+
exec_query(sql, name, binds)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def select_one(sql, name = nil, binds = [])
|
|
282
|
+
sql, binds = to_sql_and_binds(sql, binds)
|
|
283
|
+
result = exec_query(sql, name, binds)
|
|
284
|
+
result.first
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def select_value(sql, name = nil, binds = [])
|
|
288
|
+
sql, binds = to_sql_and_binds(sql, binds)
|
|
289
|
+
result = exec_query(sql, name, binds)
|
|
290
|
+
result.first&.values&.first
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def select_values(sql, name = nil, binds = [])
|
|
294
|
+
sql, binds = to_sql_and_binds(sql, binds)
|
|
295
|
+
result = exec_query(sql, name, binds)
|
|
296
|
+
result.map { |row| row.values.first }
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def select_rows(sql, name = nil, binds = [])
|
|
300
|
+
sql, binds = to_sql_and_binds(sql, binds)
|
|
301
|
+
result = exec_query(sql, name, binds)
|
|
302
|
+
result.map { |row| row.values }
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# ==================== TRANSACTION METHODS ====================
|
|
306
|
+
|
|
307
|
+
def begin_db_transaction
|
|
308
|
+
@transaction_depth += 1
|
|
309
|
+
@connection.begin_db_transaction if @transaction_depth == 1
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def commit_db_transaction
|
|
313
|
+
return if @transaction_depth <= 0
|
|
314
|
+
|
|
315
|
+
@transaction_depth -= 1
|
|
316
|
+
@connection.commit_db_transaction if @transaction_depth == 0
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def rollback_db_transaction
|
|
320
|
+
return if @transaction_depth <= 0
|
|
321
|
+
|
|
322
|
+
@transaction_depth -= 1
|
|
323
|
+
@connection.rollback_db_transaction if @transaction_depth == 0
|
|
324
|
+
@transaction_depth = 0 if @transaction_depth < 0
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def create_savepoint(name)
|
|
328
|
+
execute("SAVEPOINT #{name}")
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def rollback_to_savepoint(name)
|
|
332
|
+
execute("ROLLBACK TO SAVEPOINT #{name}")
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def release_savepoint(name)
|
|
336
|
+
execute("RELEASE SAVEPOINT #{name}")
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def in_transaction?
|
|
340
|
+
@transaction_depth > 0
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def transaction_joinable?
|
|
344
|
+
true
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def transactional?
|
|
348
|
+
true
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# ==================== SCHEMA STATEMENT METHODS ====================
|
|
352
|
+
|
|
353
|
+
def create_table(table_name, **options, &)
|
|
354
|
+
RubyDB::Rails::SchemaStatements.instance_method(:create_table).bind_call(self, table_name, options, &)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def drop_table(table_name, **options)
|
|
358
|
+
sql = +"DROP TABLE"
|
|
359
|
+
sql << " IF EXISTS" if options[:if_exists]
|
|
360
|
+
sql << " #{quote_table_name(table_name)}"
|
|
361
|
+
sql << " CASCADE" if options[:cascade]
|
|
362
|
+
execute(sql)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def add_column(table_name, column_name, type, **options)
|
|
366
|
+
sql = "ALTER TABLE #{quote_table_name(table_name)}"
|
|
367
|
+
sql << " ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options)}"
|
|
368
|
+
|
|
369
|
+
if options[:null] == false
|
|
370
|
+
sql << " NOT NULL"
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
if options[:default]
|
|
374
|
+
sql << " DEFAULT #{quote_default(options[:default])}"
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
if options[:primary_key]
|
|
378
|
+
sql << " PRIMARY KEY"
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
execute(sql)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def remove_column(table_name, column_name, type = nil, **options)
|
|
385
|
+
sql = "ALTER TABLE #{quote_table_name(table_name)}"
|
|
386
|
+
sql << " DROP COLUMN #{quote_column_name(column_name)}"
|
|
387
|
+
sql << " CASCADE" if options[:cascade]
|
|
388
|
+
execute(sql)
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def change_column(table_name, column_name, type, **options)
|
|
392
|
+
# Change column type
|
|
393
|
+
sql = "ALTER TABLE #{quote_table_name(table_name)}"
|
|
394
|
+
sql << " ALTER COLUMN #{quote_column_name(column_name)}"
|
|
395
|
+
sql << " TYPE #{type_to_sql(type, options)}"
|
|
396
|
+
execute(sql)
|
|
397
|
+
|
|
398
|
+
# Change nullability
|
|
399
|
+
if options.key?(:null)
|
|
400
|
+
sql = "ALTER TABLE #{quote_table_name(table_name)}"
|
|
401
|
+
sql << " ALTER COLUMN #{quote_column_name(column_name)}"
|
|
402
|
+
sql << (options[:null] ? " DROP" : " SET") + " NOT NULL"
|
|
403
|
+
execute(sql)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# Change default
|
|
407
|
+
if options.key?(:default)
|
|
408
|
+
sql = "ALTER TABLE #{quote_table_name(table_name)}"
|
|
409
|
+
sql << " ALTER COLUMN #{quote_column_name(column_name)}"
|
|
410
|
+
sql << if options[:default].nil?
|
|
411
|
+
" DROP DEFAULT"
|
|
412
|
+
else
|
|
413
|
+
" SET DEFAULT #{quote_default(options[:default])}"
|
|
414
|
+
end
|
|
415
|
+
execute(sql)
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def rename_column(table_name, column_name, new_column_name)
|
|
420
|
+
sql = "ALTER TABLE #{quote_table_name(table_name)}"
|
|
421
|
+
sql << " RENAME COLUMN #{quote_column_name(column_name)}"
|
|
422
|
+
sql << " TO #{quote_column_name(new_column_name)}"
|
|
423
|
+
execute(sql)
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def rename_table(old_name, new_name)
|
|
427
|
+
sql = "ALTER TABLE #{quote_table_name(old_name)}"
|
|
428
|
+
sql << " RENAME TO #{quote_table_name(new_name)}"
|
|
429
|
+
execute(sql)
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def add_index(table_name, column_name, **options)
|
|
433
|
+
index_name = options[:name] || "idx_#{table_name}_#{Array(column_name).join("_")}"
|
|
434
|
+
sql = +"CREATE"
|
|
435
|
+
sql << " UNIQUE" if options[:unique]
|
|
436
|
+
sql << " INDEX #{quote_column_name(index_name)}"
|
|
437
|
+
sql << " ON #{quote_table_name(table_name)}"
|
|
438
|
+
sql << " (#{Array(column_name).map { |c| quote_column_name(c) }.join(", ")})"
|
|
439
|
+
sql << " WHERE #{options[:where]}" if options[:where]
|
|
440
|
+
execute(sql)
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def remove_index(table_name, column_name = nil, **options)
|
|
444
|
+
index_name = options[:name]
|
|
445
|
+
if index_name.nil?
|
|
446
|
+
column_name ||= options[:column] || options[:columns]
|
|
447
|
+
index_name = "idx_#{table_name}_#{Array(column_name).join("_")}"
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
sql = "DROP INDEX #{quote_column_name(index_name)}"
|
|
451
|
+
execute(sql)
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def add_foreign_key(from_table, to_table, **options)
|
|
455
|
+
fk_name = options[:name] || "fk_#{from_table}_to_#{to_table}"
|
|
456
|
+
sql = "ALTER TABLE #{quote_table_name(from_table)}"
|
|
457
|
+
sql << " ADD CONSTRAINT #{quote_column_name(fk_name)}"
|
|
458
|
+
sql << " FOREIGN KEY (#{quote_column_name(options[:column] || :id)})"
|
|
459
|
+
sql << " REFERENCES #{quote_table_name(to_table)}"
|
|
460
|
+
sql << " (#{quote_column_name(options[:primary_key] || :id)})"
|
|
461
|
+
sql << " ON DELETE #{options[:on_delete]}" if options[:on_delete]
|
|
462
|
+
sql << " ON UPDATE #{options[:on_update]}" if options[:on_update]
|
|
463
|
+
execute(sql)
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
def foreign_keys(table_name)
|
|
467
|
+
constraints = if embedded?
|
|
468
|
+
@connection.engine.table_metadata[table_name.to_s]&.fetch(:constraints, []) || []
|
|
469
|
+
else
|
|
470
|
+
Array(remote_metadata(table_name)[:constraints])
|
|
471
|
+
end
|
|
472
|
+
constraints.filter_map do |constraint|
|
|
473
|
+
definition = constraint.transform_keys(&:to_sym)
|
|
474
|
+
next unless definition[:type].to_s.downcase.include?("foreign")
|
|
475
|
+
|
|
476
|
+
columns = definition[:columns] || []
|
|
477
|
+
reference_table = definition[:reference_table]
|
|
478
|
+
reference_columns = definition[:reference_columns] || ["id"]
|
|
479
|
+
options = {
|
|
480
|
+
column: Array(columns).first.to_s,
|
|
481
|
+
primary_key: Array(reference_columns).first.to_s,
|
|
482
|
+
name: definition[:name]
|
|
483
|
+
}
|
|
484
|
+
options[:on_delete] = definition[:on_delete].to_s if definition[:on_delete]
|
|
485
|
+
options[:on_update] = definition[:on_update].to_s if definition[:on_update]
|
|
486
|
+
ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(table_name.to_s, reference_table.to_s, options)
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
def remove_foreign_key(from_table, **options)
|
|
491
|
+
fk_name = options[:name] || "fk_#{from_table}_to_#{options[:to_table]}"
|
|
492
|
+
sql = "ALTER TABLE #{quote_table_name(from_table)}"
|
|
493
|
+
sql << " DROP CONSTRAINT #{quote_column_name(fk_name)}"
|
|
494
|
+
execute(sql)
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def add_timestamps(table_name, **options)
|
|
498
|
+
add_column(table_name, :created_at, :datetime, options)
|
|
499
|
+
add_column(table_name, :updated_at, :datetime, options)
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def remove_timestamps(table_name, **options)
|
|
503
|
+
remove_column(table_name, :updated_at, options)
|
|
504
|
+
remove_column(table_name, :created_at, options)
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
def change_column_null(table_name, column_name, null, default = nil)
|
|
508
|
+
if default
|
|
509
|
+
sql = "UPDATE #{quote_table_name(table_name)}"
|
|
510
|
+
sql << " SET #{quote_column_name(column_name)} = #{quote(default)}"
|
|
511
|
+
sql << " WHERE #{quote_column_name(column_name)} IS NULL"
|
|
512
|
+
execute(sql)
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
sql = "ALTER TABLE #{quote_table_name(table_name)}"
|
|
516
|
+
sql << " ALTER COLUMN #{quote_column_name(column_name)}"
|
|
517
|
+
sql << (null ? " DROP" : " SET") + " NOT NULL"
|
|
518
|
+
execute(sql)
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def change_column_default(table_name, column_name, default)
|
|
522
|
+
sql = "ALTER TABLE #{quote_table_name(table_name)}"
|
|
523
|
+
sql << " ALTER COLUMN #{quote_column_name(column_name)}"
|
|
524
|
+
sql << if default.nil?
|
|
525
|
+
" DROP DEFAULT"
|
|
526
|
+
else
|
|
527
|
+
" SET DEFAULT #{quote_default(default)}"
|
|
528
|
+
end
|
|
529
|
+
execute(sql)
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# ==================== QUOTING METHODS ====================
|
|
533
|
+
|
|
534
|
+
def quote(value, column = nil)
|
|
535
|
+
@connection.quote(value, column)
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
def quote_table_name(name)
|
|
539
|
+
@connection.quote_table_name(name)
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def quote_column_name(name)
|
|
543
|
+
@connection.quote_column_name(name)
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
def quote_default(value)
|
|
547
|
+
quote(value)
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
# ==================== TYPE CASTING ====================
|
|
551
|
+
|
|
552
|
+
def type_cast(value, type)
|
|
553
|
+
RubyDB::Rails::Type.serialize(value, type)
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
def type_cast_from_database(value, type)
|
|
557
|
+
RubyDB::Rails::Type.deserialize(value, type)
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
# ==================== SCHEMA VERSION ====================
|
|
561
|
+
|
|
562
|
+
def schema_version
|
|
563
|
+
result = execute("SELECT version FROM schema_migrations ORDER BY version DESC LIMIT 1")
|
|
564
|
+
result.first ? result.first["version"] : nil
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
def schema_migrations
|
|
568
|
+
result = execute("SELECT version FROM schema_migrations ORDER BY version")
|
|
569
|
+
result.map { |row| row["version"] }
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def dump_schema
|
|
573
|
+
schema = +""
|
|
574
|
+
tables.each do |table|
|
|
575
|
+
table_columns = columns(table)
|
|
576
|
+
primary_key_name = if embedded?
|
|
577
|
+
@connection.engine.table_columns(table).find(&:primary_key?)&.name
|
|
578
|
+
else
|
|
579
|
+
primary_key(table)
|
|
580
|
+
end
|
|
581
|
+
primary_key = table_columns.find { |column| column.name.to_s == primary_key_name.to_s } if primary_key_name
|
|
582
|
+
automatic_id = primary_key && primary_key_name.to_s == "id" && primary_key.type.to_sym == :integer
|
|
583
|
+
table_options = automatic_id ? "" : ", id: false"
|
|
584
|
+
schema << "create_table \"#{table}\"#{table_options} do |t|\n"
|
|
585
|
+
table_columns.each do |col|
|
|
586
|
+
next if automatic_id && primary_key && col.name.to_s == primary_key.name.to_s
|
|
587
|
+
|
|
588
|
+
type = RubyDB::Rails::Type.to_rails(col.type)
|
|
589
|
+
schema << " t.#{type} \"#{col.name}\""
|
|
590
|
+
schema << ", primary_key: true" if primary_key && col.name.to_s == primary_key.name.to_s
|
|
591
|
+
schema << ", default: #{schema_literal(col.default, col.type)}" unless col.default.nil?
|
|
592
|
+
schema << ", null: false" unless col.null
|
|
593
|
+
schema << "\n"
|
|
594
|
+
end
|
|
595
|
+
schema << "end\n\n"
|
|
596
|
+
|
|
597
|
+
indexes(table).each do |index|
|
|
598
|
+
schema << "add_index \"#{table}\", #{index.columns.map(&:to_s).inspect}"
|
|
599
|
+
schema << ", unique: true" if index.unique
|
|
600
|
+
schema << ", name: #{index.name.to_s.inspect}\n"
|
|
601
|
+
end
|
|
602
|
+
schema << "\n" if indexes(table).any?
|
|
603
|
+
end
|
|
604
|
+
schema
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
def schema_literal(value, type = nil)
|
|
608
|
+
if type.to_sym == :boolean && value.is_a?(String) && %w[true false].include?(value.downcase)
|
|
609
|
+
return value.downcase
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
case value
|
|
613
|
+
when true then "true"
|
|
614
|
+
when false then "false"
|
|
615
|
+
when Numeric then value.to_s
|
|
616
|
+
else value.to_s.inspect
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
# ==================== CONNECTION MANAGEMENT ====================
|
|
621
|
+
|
|
622
|
+
def reset!
|
|
623
|
+
@connection.disconnect
|
|
624
|
+
@connection.connect
|
|
625
|
+
@prepared_statements.clear
|
|
626
|
+
@query_cache.clear
|
|
627
|
+
@statements.clear
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
def disconnect!
|
|
631
|
+
@connection.disconnect
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
def reconnect!
|
|
635
|
+
reset!
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
def active?
|
|
639
|
+
@connection.connected?
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
def close
|
|
643
|
+
@connection.disconnect
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
# ==================== QUERY CACHE ====================
|
|
647
|
+
|
|
648
|
+
def clear_cache!
|
|
649
|
+
@query_cache.clear
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
def enable_query_cache!
|
|
653
|
+
@query_cache_enabled = true
|
|
654
|
+
@query_cache.clear
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
def disable_query_cache!
|
|
658
|
+
@query_cache_enabled = false
|
|
659
|
+
@query_cache.clear
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
attr_reader :query_cache_enabled
|
|
663
|
+
|
|
664
|
+
# ==================== PREPARED STATEMENTS ====================
|
|
665
|
+
|
|
666
|
+
def prepare_statement(sql)
|
|
667
|
+
@lock.synchronize do
|
|
668
|
+
stmt_id = "stmt_#{Time.now.to_i}_#{@statement_counter}"
|
|
669
|
+
@statement_counter += 1
|
|
670
|
+
|
|
671
|
+
result = @connection.prepare(sql)
|
|
672
|
+
@prepared_statements[stmt_id] = {
|
|
673
|
+
id: result.statement_id,
|
|
674
|
+
sql: sql,
|
|
675
|
+
created_at: Time.now
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
stmt_id
|
|
679
|
+
end
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
def execute_prepared_statement(stmt_id, params = [])
|
|
683
|
+
@lock.synchronize do
|
|
684
|
+
stmt = @prepared_statements[stmt_id]
|
|
685
|
+
return nil unless stmt
|
|
686
|
+
|
|
687
|
+
@connection.execute_prepared(stmt[:id], params)
|
|
688
|
+
end
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
def close_statement(stmt_id)
|
|
692
|
+
@lock.synchronize do
|
|
693
|
+
stmt = @prepared_statements.delete(stmt_id)
|
|
694
|
+
if stmt
|
|
695
|
+
@connection.close_statement(stmt[:id])
|
|
696
|
+
end
|
|
697
|
+
end
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
# ==================== VERSION INFORMATION ====================
|
|
701
|
+
|
|
702
|
+
def dbms_version
|
|
703
|
+
RubyDB::VERSION
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
# ==================== FEATURE SUPPORT ====================
|
|
707
|
+
|
|
708
|
+
def supports_datetime_with_precision?
|
|
709
|
+
true
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
def supports_materialized_views?
|
|
713
|
+
false
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
def supports_common_table_expressions?
|
|
717
|
+
false
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
# ==================== PRIVATE METHODS ====================
|
|
721
|
+
|
|
722
|
+
private
|
|
723
|
+
|
|
724
|
+
def embedded?
|
|
725
|
+
!@connection.engine.nil?
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
def remote_metadata(table_name = nil)
|
|
729
|
+
raw = @connection.client.metadata(table_name)
|
|
730
|
+
metadata = raw[:metadata] || raw["metadata"] || raw
|
|
731
|
+
metadata.transform_keys(&:to_sym)
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
def embedded_columns(table_name)
|
|
735
|
+
@connection.engine.table_columns(table_name).map do |column|
|
|
736
|
+
ActiveRecord::ConnectionAdapters::Column.new(
|
|
737
|
+
column.name.to_s,
|
|
738
|
+
column.has_default? ? rails_default_value(column.default) : nil,
|
|
739
|
+
ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(
|
|
740
|
+
sql_type: column.type.to_s.upcase,
|
|
741
|
+
type: rails_type_for(column.type),
|
|
742
|
+
limit: column.options[:limit]
|
|
743
|
+
),
|
|
744
|
+
column.nullable?
|
|
745
|
+
)
|
|
746
|
+
end
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
def rails_type_for(type)
|
|
750
|
+
case type.to_sym
|
|
751
|
+
when :integer, :bigint, :smallint then :integer
|
|
752
|
+
when :float then :float
|
|
753
|
+
when :decimal then :decimal
|
|
754
|
+
when :boolean then :boolean
|
|
755
|
+
when :date then :date
|
|
756
|
+
when :time then :time
|
|
757
|
+
when :datetime, :timestamp then :datetime
|
|
758
|
+
when :binary, :blob then :binary
|
|
759
|
+
when :json then :json
|
|
760
|
+
else :string
|
|
761
|
+
end
|
|
762
|
+
end
|
|
763
|
+
|
|
764
|
+
# ActiveRecord's generic Column deduplication is string-oriented. RubyDB
|
|
765
|
+
# persists typed defaults, so serialize scalar defaults at this boundary
|
|
762
766
|
# and let ActiveRecord cast them through the column type map.
|
|
763
767
|
def rails_default_value(value)
|
|
768
|
+
return nil if value.nil?
|
|
769
|
+
|
|
764
770
|
# RubyDB exposes SQL defaults as AST literals. ActiveRecord expects
|
|
765
771
|
# the scalar payload when it builds its Column metadata; calling
|
|
766
772
|
# `to_s` on the wrapper would leak the Ruby object inspection into
|
|
@@ -768,21 +774,21 @@ module ActiveRecord
|
|
|
768
774
|
value = value.value if value.respond_to?(:value) && !value.is_a?(String)
|
|
769
775
|
value.is_a?(String) ? value : value.to_s
|
|
770
776
|
end
|
|
771
|
-
|
|
772
|
-
def sql_for_execution(sql)
|
|
773
|
-
sql = sql.to_sql if sql.respond_to?(:to_sql)
|
|
774
|
-
sql
|
|
775
|
-
end
|
|
776
|
-
|
|
777
|
-
def active_record_result(result)
|
|
778
|
-
rows = result.to_a
|
|
779
|
-
columns = if rows.first.respond_to?(:keys)
|
|
780
|
-
rows.first.keys.map(&:to_s)
|
|
781
|
-
else
|
|
782
|
-
result.columns.map do |column|
|
|
783
|
-
column.is_a?(Hash) ? (column[:name] || column["name"] || column) : column
|
|
784
|
-
end.map(&:to_s)
|
|
785
|
-
end
|
|
777
|
+
|
|
778
|
+
def sql_for_execution(sql)
|
|
779
|
+
sql = sql.to_sql if sql.respond_to?(:to_sql)
|
|
780
|
+
sql
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
def active_record_result(result)
|
|
784
|
+
rows = result.to_a
|
|
785
|
+
columns = if rows.first.respond_to?(:keys)
|
|
786
|
+
rows.first.keys.map(&:to_s)
|
|
787
|
+
else
|
|
788
|
+
result.columns.map do |column|
|
|
789
|
+
column.is_a?(Hash) ? (column[:name] || column["name"] || column) : column
|
|
790
|
+
end.map(&:to_s)
|
|
791
|
+
end
|
|
786
792
|
values = rows.map do |row|
|
|
787
793
|
columns.map do |column|
|
|
788
794
|
if row.respond_to?(:key?) && row.key?(column)
|
|
@@ -792,110 +798,106 @@ module ActiveRecord
|
|
|
792
798
|
end
|
|
793
799
|
end
|
|
794
800
|
end
|
|
795
|
-
ActiveRecord::Result.new(columns, values)
|
|
796
|
-
end
|
|
797
|
-
|
|
798
|
-
# ActiveRecord normally supplies QueryAttribute objects, but migration
|
|
799
|
-
# and schema code can also pass raw values or two-element bind pairs.
|
|
800
|
-
# Normalize all supported forms at the adapter boundary.
|
|
801
|
-
def bind_values(binds)
|
|
802
|
-
binds.map do |bind|
|
|
803
|
-
value = if bind.respond_to?(:value_for_database)
|
|
804
|
-
bind.value_for_database
|
|
805
|
-
elsif bind.respond_to?(:value)
|
|
806
|
-
bind.value
|
|
807
|
-
elsif bind.is_a?(Array) && bind.length == 2
|
|
808
|
-
bind.last
|
|
809
|
-
else
|
|
810
|
-
bind
|
|
811
|
-
end
|
|
812
|
-
value.respond_to?(:value_for_database) ? value.value_for_database : value
|
|
813
|
-
end
|
|
814
|
-
end
|
|
815
|
-
|
|
816
|
-
def parse_index_columns(sql)
|
|
817
|
-
if sql =~ /\(([^)]+)\)/
|
|
818
|
-
$1.split(",").map(&:strip)
|
|
819
|
-
else
|
|
820
|
-
[]
|
|
821
|
-
end
|
|
822
|
-
end
|
|
823
|
-
|
|
824
|
-
def extract_limit(type)
|
|
825
|
-
if type =~ /VARCHAR\((\d+)\)/
|
|
826
|
-
$1.to_i
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
when :
|
|
847
|
-
"
|
|
848
|
-
when :
|
|
849
|
-
"
|
|
850
|
-
when :
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
"
|
|
854
|
-
when :
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
"
|
|
858
|
-
when :
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
"
|
|
865
|
-
when :
|
|
866
|
-
"
|
|
867
|
-
when :
|
|
868
|
-
"
|
|
869
|
-
when :
|
|
870
|
-
"
|
|
871
|
-
when :
|
|
872
|
-
"
|
|
873
|
-
|
|
874
|
-
"
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
def
|
|
893
|
-
|
|
894
|
-
end
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
end
|
|
899
|
-
end
|
|
900
|
-
end
|
|
901
|
-
end
|
|
801
|
+
ActiveRecord::Result.new(columns, values)
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
# ActiveRecord normally supplies QueryAttribute objects, but migration
|
|
805
|
+
# and schema code can also pass raw values or two-element bind pairs.
|
|
806
|
+
# Normalize all supported forms at the adapter boundary.
|
|
807
|
+
def bind_values(binds)
|
|
808
|
+
binds.map do |bind|
|
|
809
|
+
value = if bind.respond_to?(:value_for_database)
|
|
810
|
+
bind.value_for_database
|
|
811
|
+
elsif bind.respond_to?(:value)
|
|
812
|
+
bind.value
|
|
813
|
+
elsif bind.is_a?(Array) && bind.length == 2
|
|
814
|
+
bind.last
|
|
815
|
+
else
|
|
816
|
+
bind
|
|
817
|
+
end
|
|
818
|
+
value.respond_to?(:value_for_database) ? value.value_for_database : value
|
|
819
|
+
end
|
|
820
|
+
end
|
|
821
|
+
|
|
822
|
+
def parse_index_columns(sql)
|
|
823
|
+
if sql =~ /\(([^)]+)\)/
|
|
824
|
+
$1.split(",").map(&:strip)
|
|
825
|
+
else
|
|
826
|
+
[]
|
|
827
|
+
end
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
def extract_limit(type)
|
|
831
|
+
if type =~ /VARCHAR\((\d+)\)/
|
|
832
|
+
$1.to_i
|
|
833
|
+
end
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
def log(sql, name = nil, &block)
|
|
837
|
+
start_time = Time.now
|
|
838
|
+
result = block.call
|
|
839
|
+
elapsed_ms = (Time.now - start_time) * 1000
|
|
840
|
+
|
|
841
|
+
@logger&.debug " #{name || "SQL"} (#{elapsed_ms.round(2)}ms) #{sql}"
|
|
842
|
+
|
|
843
|
+
result
|
|
844
|
+
end
|
|
845
|
+
|
|
846
|
+
def type_to_sql(type, options = {})
|
|
847
|
+
case type.to_sym
|
|
848
|
+
when :integer
|
|
849
|
+
"INTEGER"
|
|
850
|
+
when :bigint
|
|
851
|
+
"BIGINT"
|
|
852
|
+
when :smallint
|
|
853
|
+
"SMALLINT"
|
|
854
|
+
when :float
|
|
855
|
+
"FLOAT"
|
|
856
|
+
when :decimal
|
|
857
|
+
precision = options[:precision] || 10
|
|
858
|
+
scale = options[:scale] || 2
|
|
859
|
+
"DECIMAL(#{precision}, #{scale})"
|
|
860
|
+
when :boolean
|
|
861
|
+
"BOOLEAN"
|
|
862
|
+
when :text
|
|
863
|
+
"TEXT"
|
|
864
|
+
when :string
|
|
865
|
+
limit = options[:limit] || 255
|
|
866
|
+
"VARCHAR(#{limit})"
|
|
867
|
+
when :binary
|
|
868
|
+
"BLOB"
|
|
869
|
+
when :date
|
|
870
|
+
"DATE"
|
|
871
|
+
when :time
|
|
872
|
+
"TIME"
|
|
873
|
+
when :datetime, :timestamp
|
|
874
|
+
"TIMESTAMP"
|
|
875
|
+
when :json
|
|
876
|
+
"JSON"
|
|
877
|
+
when :uuid
|
|
878
|
+
"UUID"
|
|
879
|
+
else
|
|
880
|
+
"TEXT"
|
|
881
|
+
end
|
|
882
|
+
end
|
|
883
|
+
end
|
|
884
|
+
end
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
# Rails 7.2 introduced explicit adapter registration. Rails 7.1 loads custom
|
|
888
|
+
# adapters through the conventional `rubydb_connection` hook instead.
|
|
889
|
+
if ActiveRecord::ConnectionAdapters.respond_to?(:register)
|
|
890
|
+
ActiveRecord::ConnectionAdapters.register("rubydb", "ActiveRecord::ConnectionAdapters::RubyDBAdapter")
|
|
891
|
+
else
|
|
892
|
+
module ActiveRecord
|
|
893
|
+
module ConnectionHandling
|
|
894
|
+
def rubydb_adapter_class
|
|
895
|
+
ConnectionAdapters::RubyDBAdapter
|
|
896
|
+
end
|
|
897
|
+
|
|
898
|
+
def rubydb_connection(config)
|
|
899
|
+
rubydb_adapter_class.new(config)
|
|
900
|
+
end
|
|
901
|
+
end
|
|
902
|
+
end
|
|
903
|
+
end
|