activerecord7-redshift-adapter-pennylane 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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_.*'
|