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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6815f12cf77e7328f50e3a2ada99bbb916ca2fef0864951950254ac5a1126c13
4
- data.tar.gz: 00d93584414a5466d84a85c96a07eaef16cfe112f493d07aab8ac6d68278dc41
3
+ metadata.gz: e8108a9c785d7604af14ffbaca6265c6350c119406c09951c0a73a2aba888471
4
+ data.tar.gz: a71d3843e77264799115393dd6ba8bb50717722c292aab9e3e73b0d0463d634e
5
5
  SHA512:
6
- metadata.gz: 15dfb8bb0b0b6aa0157bb4013f16f1d73775cee2460688efffb968479f8f1c9cc560a63fe615c44cd28699a441f6ad1e659844ad941f95090c0c058b4088b90b
7
- data.tar.gz: b3c3ee936bc36246df51c10823723a15879cd47901db1971c4db86ec02a419f61c98d32f8ddae64325c7b4780d087e327324d21382aabf6d8494c511776beb2a
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 the list of all tables in the schema search path or a specified schema.
61
- def tables(name = nil)
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
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
87
- #table_exists? currently checks both tables and views.
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
- data_source_exists?(name)
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 == 'f', table_name, default_function)
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
- select_value(<<-SQL, 'SCHEMA')
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 = "EXPLAIN #{to_sql(arel, binds)}"
9
- ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
10
- end
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
- def select_value(arel, name = nil, binds = [])
50
- # In Rails 5.2, arel_from_relation replaced binds_from_relation,
51
- # so we see which method exists to get the variables
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
- execute_and_clear(sql, name, binds) do |result|
82
- if result.nfields > 0
83
- result.column_values(0)
84
- else
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
- # Executes a SELECT query and returns an array of rows. Each row is an
91
- # array of field values.
92
- def select_rows(arel, name = nil, binds = [])
93
- if respond_to?(:arel_from_relation, true)
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
- # Queries the database and returns the results in an Array-like object
151
- def query(sql, name = nil) # :nodoc:
152
- log(sql, name) do
153
- result_as_array @raw_connection.async_exec(sql)
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
- def execute(sql, name = nil)
160
- log(sql, name) do
161
- @raw_connection.async_exec(sql)
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 exec_query(sql, name = 'SQL', binds = [], prepare: false)
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
- ActiveRecord::Result.new(fields, result.values, types)
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, &:cmd_tuples)
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 sql_for_insert(sql, pk, binds, returning)
185
- if pk.nil?
186
- # Extract the table from the insert sql. Yuck.
187
- table_ref = extract_table_ref_from_insert_sql(sql)
188
- pk = primary_key(table_ref) if table_ref
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
- sequence_name = default_sequence_name(table_ref, pk)
202
- return val unless sequence_name
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
- execute 'BEGIN'
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
- execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
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
- execute 'COMMIT'
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
- execute 'ROLLBACK'
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
- @raw_connection.escape_bytea(value) if value
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
- @raw_connection.unescape_bytea(value) if value
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
- @raw_connection.escape(s)
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(name.to_s)
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('%04d', -value.year + 1)
56
- result = "#{result.sub(/^-?\d+/, bce_year)} BC"
58
+ bce_year = format("%04d", -value.year + 1)
59
+ super.sub(/^-?\d+/, bce_year) + " BC"
60
+ else
61
+ super
57
62
  end
58
- result
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 Type::Binary::Data
73
- "'#{escape_bytea(value.to_s)}'"
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 the list of all tables in the schema search path or a specified schema.
61
- def tables(name = nil)
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
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
87
- #table_exists? currently checks both tables and views.
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
- data_source_exists?(name)
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 == 'f', table_name, default_function)
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
- select_value(<<-SQL, 'SCHEMA')
186
+ select_values(<<-SQL, 'SCHEMA')
189
187
  SELECT nspname
190
188
  FROM pg_namespace
191
189
  WHERE nspname !~ '^pg_.*'