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 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_.*'