activerecord7-redshift-adapter-pennylane 1.0.2 → 1.0.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/lib/active_record/connection_adapters/redshift_7_0/schema_statements.rb +14 -16
- data/lib/active_record/connection_adapters/redshift_7_1/database_statements.rb +122 -174
- data/lib/active_record/connection_adapters/redshift_7_1/quoting.rb +78 -16
- data/lib/active_record/connection_adapters/redshift_7_1/schema_statements.rb +14 -16
- data/lib/active_record/connection_adapters/redshift_7_1/type_metadata.rb +18 -14
- data/lib/active_record/connection_adapters/redshift_7_1_adapter.rb +257 -178
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e8108a9c785d7604af14ffbaca6265c6350c119406c09951c0a73a2aba888471
|
4
|
+
data.tar.gz: a71d3843e77264799115393dd6ba8bb50717722c292aab9e3e73b0d0463d634e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e4fd3c18f5255e06b5e0cb855c516b538304e00de9293108fde3eeb2fa7a90df2539824259af6201b4b14cd3c2d0b148c7357e29bdf00559cfcf1e700cb23ea5
|
7
|
+
data.tar.gz: 96c5852565ca6416135a7a19b49fc384007fbfb7a2bed043dd3d4804caaa765c5ba8fde811e5dc298531bc971d291acdf513b4708276fcddb76f2de489060dbe
|
@@ -57,14 +57,8 @@ module ActiveRecord
|
|
57
57
|
execute "DROP DATABASE #{quote_table_name(name)}"
|
58
58
|
end
|
59
59
|
|
60
|
-
# Returns
|
61
|
-
def tables
|
62
|
-
if name
|
63
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
64
|
-
Passing arguments to #tables is deprecated without replacement.
|
65
|
-
MSG
|
66
|
-
end
|
67
|
-
|
60
|
+
# Returns an array of table names defined in the database.
|
61
|
+
def tables
|
68
62
|
select_values('SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))', 'SCHEMA')
|
69
63
|
end
|
70
64
|
|
@@ -83,13 +77,17 @@ module ActiveRecord
|
|
83
77
|
# If the schema is not specified as part of +name+ then it will only find tables within
|
84
78
|
# the current schema search path (regardless of permissions to access tables in other schemas)
|
85
79
|
def table_exists?(name)
|
86
|
-
|
87
|
-
|
88
|
-
This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
|
89
|
-
Use #data_source_exists? instead.
|
90
|
-
MSG
|
80
|
+
name = Utils.extract_schema_qualified_name(name.to_s)
|
81
|
+
return false unless name.identifier
|
91
82
|
|
92
|
-
|
83
|
+
select_value(<<-SQL, 'SCHEMA').to_i > 0
|
84
|
+
SELECT COUNT(*)
|
85
|
+
FROM pg_class c
|
86
|
+
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
87
|
+
WHERE c.relkind = 'r' -- (r)elation/table
|
88
|
+
AND c.relname = '#{name.identifier}'
|
89
|
+
AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
|
90
|
+
SQL
|
93
91
|
end
|
94
92
|
|
95
93
|
def data_source_exists?(name)
|
@@ -154,7 +152,7 @@ module ActiveRecord
|
|
154
152
|
default_value = extract_value_from_default(default)
|
155
153
|
type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
|
156
154
|
default_function = extract_default_function(default_value, default)
|
157
|
-
new_column(column_name, default_value, type_metadata, notnull
|
155
|
+
new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function)
|
158
156
|
end
|
159
157
|
end
|
160
158
|
|
@@ -185,7 +183,7 @@ module ActiveRecord
|
|
185
183
|
|
186
184
|
# Returns an array of schema names.
|
187
185
|
def schema_names
|
188
|
-
|
186
|
+
select_values(<<-SQL, 'SCHEMA')
|
189
187
|
SELECT nspname
|
190
188
|
FROM pg_namespace
|
191
189
|
WHERE nspname !~ '^pg_.*'
|
@@ -4,227 +4,175 @@ module ActiveRecord
|
|
4
4
|
module ConnectionAdapters
|
5
5
|
module Redshift
|
6
6
|
module DatabaseStatements
|
7
|
-
def explain(arel, binds = [])
|
8
|
-
sql
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
class ExplainPrettyPrinter # :nodoc:
|
13
|
-
# Pretty prints the result of a EXPLAIN in a way that resembles the output of the
|
14
|
-
# PostgreSQL shell:
|
15
|
-
#
|
16
|
-
# QUERY PLAN
|
17
|
-
# ------------------------------------------------------------------------------
|
18
|
-
# Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
|
19
|
-
# Join Filter: (posts.user_id = users.id)
|
20
|
-
# -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
|
21
|
-
# Index Cond: (id = 1)
|
22
|
-
# -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
|
23
|
-
# Filter: (posts.user_id = 1)
|
24
|
-
# (6 rows)
|
25
|
-
#
|
26
|
-
def pp(result)
|
27
|
-
header = result.columns.first
|
28
|
-
lines = result.rows.map(&:first)
|
29
|
-
|
30
|
-
# We add 2 because there's one char of padding at both sides, note
|
31
|
-
# the extra hyphens in the example above.
|
32
|
-
width = [header, *lines].map(&:length).max + 2
|
33
|
-
|
34
|
-
pp = []
|
35
|
-
|
36
|
-
pp << header.center(width).rstrip
|
37
|
-
pp << '-' * width
|
38
|
-
|
39
|
-
pp += lines.map { |line| " #{line}" }
|
40
|
-
|
41
|
-
nrows = result.rows.length
|
42
|
-
rows_label = nrows == 1 ? 'row' : 'rows'
|
43
|
-
pp << "(#{nrows} #{rows_label})"
|
44
|
-
|
45
|
-
"#{pp.join("\n")}\n"
|
46
|
-
end
|
7
|
+
def explain(arel, binds = [], options = [])
|
8
|
+
sql = build_explain_clause(options) + " " + to_sql(arel, binds)
|
9
|
+
result = internal_exec_query(sql, "EXPLAIN", binds)
|
10
|
+
PostgreSQL::ExplainPrettyPrinter.new.pp(result)
|
47
11
|
end
|
48
12
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
#
|
53
|
-
# In Rails 6.0 to_sql_and_binds began only returning sql, with
|
54
|
-
# to_sql_and_binds serving as a replacement
|
55
|
-
if respond_to?(:arel_from_relation, true)
|
56
|
-
arel = arel_from_relation(arel)
|
57
|
-
sql, binds = to_sql_and_binds(arel, binds)
|
58
|
-
else
|
59
|
-
arel, binds = binds_from_relation arel, binds
|
60
|
-
sql = to_sql(arel, binds)
|
61
|
-
end
|
62
|
-
execute_and_clear(sql, name, binds) do |result|
|
63
|
-
result.getvalue(0, 0) if result.ntuples > 0 && result.nfields > 0
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def select_values(arel, name = nil)
|
68
|
-
# In Rails 5.2, arel_from_relation replaced binds_from_relation,
|
69
|
-
# so we see which method exists to get the variables
|
70
|
-
#
|
71
|
-
# In Rails 6.0 to_sql_and_binds began only returning sql, with
|
72
|
-
# to_sql_and_binds serving as a replacement
|
73
|
-
if respond_to?(:arel_from_relation, true)
|
74
|
-
arel = arel_from_relation(arel)
|
75
|
-
sql, binds = to_sql_and_binds(arel, [])
|
76
|
-
else
|
77
|
-
arel, binds = binds_from_relation arel, []
|
78
|
-
sql = to_sql(arel, binds)
|
79
|
-
end
|
13
|
+
# Queries the database and returns the results in an Array-like object
|
14
|
+
def query(sql, name = nil) # :nodoc:
|
15
|
+
mark_transaction_written_if_write(sql)
|
80
16
|
|
81
|
-
|
82
|
-
|
83
|
-
result.
|
84
|
-
|
85
|
-
|
17
|
+
log(sql, name) do
|
18
|
+
with_raw_connection do |conn|
|
19
|
+
result = conn.async_exec(sql).map_types!(@type_map_for_results).values
|
20
|
+
verified!
|
21
|
+
result
|
86
22
|
end
|
87
23
|
end
|
88
24
|
end
|
89
25
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
arel = arel_from_relation(arel)
|
95
|
-
sql, binds = to_sql_and_binds(arel, [])
|
96
|
-
else
|
97
|
-
arel, binds = binds_from_relation arel, []
|
98
|
-
sql = to_sql(arel, binds)
|
99
|
-
end
|
100
|
-
execute_and_clear(sql, name, binds, &:values)
|
101
|
-
end
|
102
|
-
|
103
|
-
# The internal PostgreSQL identifier of the money data type.
|
104
|
-
MONEY_COLUMN_TYPE_OID = 790 # :nodoc:
|
105
|
-
# The internal PostgreSQL identifier of the BYTEA data type.
|
106
|
-
BYTEA_COLUMN_TYPE_OID = 17 # :nodoc:
|
107
|
-
|
108
|
-
# create a 2D array representing the result set
|
109
|
-
def result_as_array(res) # :nodoc:
|
110
|
-
# check if we have any binary column and if they need escaping
|
111
|
-
ftypes = Array.new(res.nfields) do |i|
|
112
|
-
[i, res.ftype(i)]
|
113
|
-
end
|
114
|
-
|
115
|
-
rows = res.values
|
116
|
-
return rows unless ftypes.any? do |_, x|
|
117
|
-
[BYTEA_COLUMN_TYPE_OID, MONEY_COLUMN_TYPE_OID].include?(x)
|
118
|
-
end
|
119
|
-
|
120
|
-
typehash = ftypes.group_by { |_, type| type }
|
121
|
-
binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
|
122
|
-
monies = typehash[MONEY_COLUMN_TYPE_OID] || []
|
123
|
-
|
124
|
-
rows.each do |row|
|
125
|
-
# unescape string passed BYTEA field (OID == 17)
|
126
|
-
binaries.each do |index, _|
|
127
|
-
row[index] = unescape_bytea(row[index])
|
128
|
-
end
|
129
|
-
|
130
|
-
# If this is a money type column and there are any currency symbols,
|
131
|
-
# then strip them off. Indeed it would be prettier to do this in
|
132
|
-
# PostgreSQLColumn.string_to_decimal but would break form input
|
133
|
-
# fields that call value_before_type_cast.
|
134
|
-
monies.each do |index, _|
|
135
|
-
data = row[index]
|
136
|
-
# Because money output is formatted according to the locale, there are two
|
137
|
-
# cases to consider (note the decimal separators):
|
138
|
-
# (1) $12,345,678.12
|
139
|
-
# (2) $12.345.678,12
|
140
|
-
case data
|
141
|
-
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
|
142
|
-
data.gsub!(/[^-\d.]/, '')
|
143
|
-
when /^-?\D+[\d.]+,\d{2}$/ # (2)
|
144
|
-
data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
26
|
+
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
27
|
+
:close, :declare, :fetch, :move, :set, :show
|
28
|
+
) # :nodoc:
|
29
|
+
private_constant :READ_QUERY
|
149
30
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
end
|
31
|
+
def write_query?(sql) # :nodoc:
|
32
|
+
!READ_QUERY.match?(sql)
|
33
|
+
rescue ArgumentError # Invalid encoding
|
34
|
+
!READ_QUERY.match?(sql.b)
|
155
35
|
end
|
156
36
|
|
157
37
|
# Executes an SQL statement, returning a PG::Result object on success
|
158
38
|
# or raising a PG::Error exception otherwise.
|
159
|
-
|
160
|
-
|
161
|
-
|
39
|
+
#
|
40
|
+
# Setting +allow_retry+ to true causes the db to reconnect and retry
|
41
|
+
# executing the SQL statement in case of a connection-related exception.
|
42
|
+
# This option should only be enabled for known idempotent queries.
|
43
|
+
#
|
44
|
+
# Note: the PG::Result object is manually memory managed; if you don't
|
45
|
+
# need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
|
46
|
+
def execute(...) # :nodoc:
|
47
|
+
super
|
48
|
+
ensure
|
49
|
+
@notice_receiver_sql_warnings = []
|
50
|
+
end
|
51
|
+
|
52
|
+
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
53
|
+
log(sql, name, async: async) do
|
54
|
+
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
55
|
+
result = conn.async_exec(sql)
|
56
|
+
verified!
|
57
|
+
handle_warnings(result)
|
58
|
+
result
|
59
|
+
end
|
162
60
|
end
|
163
61
|
end
|
164
62
|
|
165
|
-
def
|
166
|
-
execute_and_clear(sql, name, binds, prepare: prepare) do |result|
|
63
|
+
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true) # :nodoc:
|
64
|
+
execute_and_clear(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |result|
|
167
65
|
types = {}
|
168
66
|
fields = result.fields
|
169
67
|
fields.each_with_index do |fname, i|
|
170
68
|
ftype = result.ftype i
|
171
69
|
fmod = result.fmod i
|
172
|
-
types[fname] = get_oid_type(ftype, fmod, fname)
|
70
|
+
types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
|
173
71
|
end
|
174
|
-
|
72
|
+
build_result(columns: fields, rows: result.values, column_types: types)
|
175
73
|
end
|
176
74
|
end
|
177
|
-
alias internal_exec_query exec_query
|
178
75
|
|
179
|
-
def exec_delete(sql, name = nil, binds = [])
|
180
|
-
execute_and_clear(sql, name, binds
|
76
|
+
def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
77
|
+
execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
|
181
78
|
end
|
182
|
-
alias exec_update exec_delete
|
79
|
+
alias :exec_update :exec_delete
|
183
80
|
|
184
|
-
def
|
185
|
-
if pk
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
end
|
190
|
-
|
191
|
-
sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk && use_insert_returning?
|
192
|
-
|
193
|
-
super
|
194
|
-
end
|
195
|
-
|
196
|
-
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil)
|
197
|
-
val = exec_query(sql, name, binds)
|
198
|
-
if !use_insert_returning? && pk
|
81
|
+
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
|
82
|
+
if use_insert_returning? || pk == false
|
83
|
+
super
|
84
|
+
else
|
85
|
+
result = internal_exec_query(sql, name, binds)
|
199
86
|
unless sequence_name
|
200
87
|
table_ref = extract_table_ref_from_insert_sql(sql)
|
201
|
-
|
202
|
-
|
88
|
+
if table_ref
|
89
|
+
pk = primary_key(table_ref) if pk.nil?
|
90
|
+
pk = suppress_composite_primary_key(pk)
|
91
|
+
sequence_name = default_sequence_name(table_ref, pk)
|
92
|
+
end
|
93
|
+
return result unless sequence_name
|
203
94
|
end
|
204
95
|
last_insert_id_result(sequence_name)
|
205
|
-
else
|
206
|
-
val
|
207
96
|
end
|
208
97
|
end
|
209
98
|
|
210
99
|
# Begins a transaction.
|
211
100
|
def begin_db_transaction
|
212
|
-
|
101
|
+
internal_execute('BEGIN', allow_retry: true, materialize_transactions: false)
|
213
102
|
end
|
214
103
|
|
215
104
|
def begin_isolated_db_transaction(isolation)
|
216
105
|
begin_db_transaction
|
217
|
-
|
106
|
+
internal_execute("SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", allow_retry: true, materialize_transactions: false)
|
218
107
|
end
|
219
108
|
|
220
109
|
# Commits a transaction.
|
221
110
|
def commit_db_transaction
|
222
|
-
|
111
|
+
internal_execute('COMMIT', allow_retry: false, materialize_transactions: true)
|
223
112
|
end
|
224
113
|
|
225
114
|
# Aborts a transaction.
|
226
115
|
def exec_rollback_db_transaction
|
227
|
-
|
116
|
+
internal_execute('ROLLBACK', allow_retry: false, materialize_transactions: true)
|
117
|
+
end
|
118
|
+
|
119
|
+
def exec_restart_db_transaction # :nodoc:
|
120
|
+
cancel_any_running_query
|
121
|
+
exec_rollback_db_transaction
|
122
|
+
begin_db_transaction
|
123
|
+
end
|
124
|
+
|
125
|
+
# From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
|
126
|
+
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
|
127
|
+
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
|
128
|
+
|
129
|
+
def high_precision_current_timestamp
|
130
|
+
HIGH_PRECISION_CURRENT_TIMESTAMP
|
131
|
+
end
|
132
|
+
|
133
|
+
def build_explain_clause(options = [])
|
134
|
+
return "EXPLAIN" if options.empty?
|
135
|
+
|
136
|
+
"EXPLAIN (#{options.join(", ").upcase})"
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
IDLE_TRANSACTION_STATUSES = [PG::PQTRANS_IDLE, PG::PQTRANS_INTRANS, PG::PQTRANS_INERROR]
|
142
|
+
private_constant :IDLE_TRANSACTION_STATUSES
|
143
|
+
|
144
|
+
def cancel_any_running_query
|
145
|
+
return if @raw_connection.nil? || IDLE_TRANSACTION_STATUSES.include?(@raw_connection.transaction_status)
|
146
|
+
|
147
|
+
@raw_connection.cancel
|
148
|
+
@raw_connection.block
|
149
|
+
rescue PG::Error
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns the current ID of a table's sequence.
|
153
|
+
def last_insert_id_result(sequence_name)
|
154
|
+
internal_exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
|
155
|
+
end
|
156
|
+
|
157
|
+
def returning_column_values(result)
|
158
|
+
result.rows.first
|
159
|
+
end
|
160
|
+
|
161
|
+
def suppress_composite_primary_key(pk)
|
162
|
+
pk unless pk.is_a?(Array)
|
163
|
+
end
|
164
|
+
|
165
|
+
def handle_warnings(sql)
|
166
|
+
@notice_receiver_sql_warnings.each do |warning|
|
167
|
+
next if warning_ignored?(warning)
|
168
|
+
|
169
|
+
warning.sql = sql
|
170
|
+
ActiveRecord.db_warnings_action.call(warning)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def warning_ignored?(warning)
|
175
|
+
["WARNING", "ERROR", "FATAL", "PANIC"].exclude?(warning.level) || super
|
228
176
|
end
|
229
177
|
end
|
230
178
|
end
|
@@ -4,21 +4,26 @@ module ActiveRecord
|
|
4
4
|
module ConnectionAdapters
|
5
5
|
module Redshift
|
6
6
|
module Quoting
|
7
|
+
QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
|
8
|
+
QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
|
9
|
+
|
7
10
|
# Escapes binary strings for bytea input to the database.
|
8
11
|
def escape_bytea(value)
|
9
|
-
|
12
|
+
valid_raw_connection.escape_bytea(value) if value
|
10
13
|
end
|
11
14
|
|
12
15
|
# Unescapes bytea output from a database to the binary string it represents.
|
13
16
|
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
|
14
17
|
# on escaped binary output from database drive.
|
15
18
|
def unescape_bytea(value)
|
16
|
-
|
19
|
+
valid_raw_connection.unescape_bytea(value) if value
|
17
20
|
end
|
18
21
|
|
19
22
|
# Quotes strings for use in SQL input.
|
20
23
|
def quote_string(s) # :nodoc:
|
21
|
-
|
24
|
+
with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
|
25
|
+
connection.escape(s)
|
26
|
+
end
|
22
27
|
end
|
23
28
|
|
24
29
|
# Checks the following cases:
|
@@ -30,7 +35,7 @@ module ActiveRecord
|
|
30
35
|
# - "schema.name".table_name
|
31
36
|
# - "schema.name"."table.name"
|
32
37
|
def quote_table_name(name)
|
33
|
-
Utils.extract_schema_qualified_name(name.to_s).quoted
|
38
|
+
QUOTED_TABLE_NAMES[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
|
34
39
|
end
|
35
40
|
|
36
41
|
def quote_table_name_for_assignment(_table, attr)
|
@@ -39,7 +44,7 @@ module ActiveRecord
|
|
39
44
|
|
40
45
|
# Quotes column names for use in SQL queries.
|
41
46
|
def quote_column_name(name) # :nodoc:
|
42
|
-
PG::Connection.quote_ident(
|
47
|
+
QUOTED_COLUMN_NAMES[name] ||= PG::Connection.quote_ident(super).freeze
|
43
48
|
end
|
44
49
|
|
45
50
|
# Quotes schema names for use in SQL queries.
|
@@ -49,13 +54,16 @@ module ActiveRecord
|
|
49
54
|
|
50
55
|
# Quote date/time values for use in SQL input.
|
51
56
|
def quoted_date(value) # :nodoc:
|
52
|
-
result = super
|
53
|
-
|
54
57
|
if value.year <= 0
|
55
|
-
bce_year = format(
|
56
|
-
|
58
|
+
bce_year = format("%04d", -value.year + 1)
|
59
|
+
super.sub(/^-?\d+/, bce_year) + " BC"
|
60
|
+
else
|
61
|
+
super
|
57
62
|
end
|
58
|
-
|
63
|
+
end
|
64
|
+
|
65
|
+
def quoted_binary(value) # :nodoc:
|
66
|
+
"'#{escape_bytea(value.to_s)}'"
|
59
67
|
end
|
60
68
|
|
61
69
|
# Does not quote function default values for UUID columns
|
@@ -69,14 +77,24 @@ module ActiveRecord
|
|
69
77
|
|
70
78
|
def quote(value)
|
71
79
|
case value
|
72
|
-
when
|
73
|
-
|
74
|
-
when Float
|
75
|
-
if value.infinite? || value.nan?
|
76
|
-
"'#{value}'"
|
77
|
-
else
|
80
|
+
when Numeric
|
81
|
+
if value.finite?
|
78
82
|
super
|
83
|
+
else
|
84
|
+
"'#{value}'"
|
79
85
|
end
|
86
|
+
when Type::Binary::Data
|
87
|
+
"'#{escape_bytea(value.to_s)}'"
|
88
|
+
else
|
89
|
+
super
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def quote_default_expression(value, column) # :nodoc:
|
94
|
+
if value.is_a?(Proc)
|
95
|
+
value.call
|
96
|
+
elsif column.type == :uuid && value.is_a?(String) && value.include?("()")
|
97
|
+
value # Does not quote function default values for UUID columns
|
80
98
|
else
|
81
99
|
super
|
82
100
|
end
|
@@ -93,6 +111,50 @@ module ActiveRecord
|
|
93
111
|
super
|
94
112
|
end
|
95
113
|
end
|
114
|
+
|
115
|
+
def lookup_cast_type_from_column(column) # :nodoc:
|
116
|
+
verify! if type_map.nil?
|
117
|
+
type_map.lookup(column.oid, column.fmod, column.sql_type)
|
118
|
+
end
|
119
|
+
|
120
|
+
def column_name_matcher
|
121
|
+
COLUMN_NAME
|
122
|
+
end
|
123
|
+
|
124
|
+
def column_name_with_order_matcher
|
125
|
+
COLUMN_NAME_WITH_ORDER
|
126
|
+
end
|
127
|
+
|
128
|
+
COLUMN_NAME = /
|
129
|
+
\A
|
130
|
+
(
|
131
|
+
(?:
|
132
|
+
# "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
|
133
|
+
((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
|
134
|
+
)
|
135
|
+
(?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
|
136
|
+
)
|
137
|
+
(?:\s*,\s*\g<1>)*
|
138
|
+
\z
|
139
|
+
/ix
|
140
|
+
|
141
|
+
COLUMN_NAME_WITH_ORDER = /
|
142
|
+
\A
|
143
|
+
(
|
144
|
+
(?:
|
145
|
+
# "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
|
146
|
+
((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
|
147
|
+
)
|
148
|
+
(?:\s+COLLATE\s+"\w+")?
|
149
|
+
(?:\s+ASC|\s+DESC)?
|
150
|
+
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
151
|
+
)
|
152
|
+
(?:\s*,\s*\g<1>)*
|
153
|
+
\z
|
154
|
+
/ix
|
155
|
+
|
156
|
+
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
|
157
|
+
|
96
158
|
end
|
97
159
|
end
|
98
160
|
end
|
@@ -57,14 +57,8 @@ module ActiveRecord
|
|
57
57
|
execute "DROP DATABASE #{quote_table_name(name)}"
|
58
58
|
end
|
59
59
|
|
60
|
-
# Returns
|
61
|
-
def tables
|
62
|
-
if name
|
63
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
64
|
-
Passing arguments to #tables is deprecated without replacement.
|
65
|
-
MSG
|
66
|
-
end
|
67
|
-
|
60
|
+
# Returns an array of table names defined in the database.
|
61
|
+
def tables
|
68
62
|
select_values('SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))', 'SCHEMA')
|
69
63
|
end
|
70
64
|
|
@@ -83,13 +77,17 @@ module ActiveRecord
|
|
83
77
|
# If the schema is not specified as part of +name+ then it will only find tables within
|
84
78
|
# the current schema search path (regardless of permissions to access tables in other schemas)
|
85
79
|
def table_exists?(name)
|
86
|
-
|
87
|
-
|
88
|
-
This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
|
89
|
-
Use #data_source_exists? instead.
|
90
|
-
MSG
|
80
|
+
name = Utils.extract_schema_qualified_name(name.to_s)
|
81
|
+
return false unless name.identifier
|
91
82
|
|
92
|
-
|
83
|
+
select_value(<<-SQL, 'SCHEMA').to_i > 0
|
84
|
+
SELECT COUNT(*)
|
85
|
+
FROM pg_class c
|
86
|
+
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
87
|
+
WHERE c.relkind = 'r' -- (r)elation/table
|
88
|
+
AND c.relname = '#{name.identifier}'
|
89
|
+
AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
|
90
|
+
SQL
|
93
91
|
end
|
94
92
|
|
95
93
|
def data_source_exists?(name)
|
@@ -154,7 +152,7 @@ module ActiveRecord
|
|
154
152
|
default_value = extract_value_from_default(default)
|
155
153
|
type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
|
156
154
|
default_function = extract_default_function(default_value, default)
|
157
|
-
new_column(column_name, default_value, type_metadata, notnull
|
155
|
+
new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function)
|
158
156
|
end
|
159
157
|
end
|
160
158
|
|
@@ -185,7 +183,7 @@ module ActiveRecord
|
|
185
183
|
|
186
184
|
# Returns an array of schema names.
|
187
185
|
def schema_names
|
188
|
-
|
186
|
+
select_values(<<-SQL, 'SCHEMA')
|
189
187
|
SELECT nspname
|
190
188
|
FROM pg_namespace
|
191
189
|
WHERE nspname !~ '^pg_.*'
|