ovirt_metrics 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/column.rb +15 -0
  3. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/database_statements.rb +170 -0
  4. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/explain_pretty_printer.rb +42 -0
  5. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/array.rb +70 -0
  6. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/bit.rb +52 -0
  7. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/bit_varying.rb +13 -0
  8. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/bytea.rb +15 -0
  9. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/cidr.rb +48 -0
  10. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/date_time.rb +21 -0
  11. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/decimal.rb +13 -0
  12. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/enum.rb +19 -0
  13. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/hstore.rb +59 -0
  14. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/inet.rb +13 -0
  15. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/json.rb +10 -0
  16. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/jsonb.rb +23 -0
  17. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/money.rb +39 -0
  18. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/point.rb +43 -0
  19. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/rails_5_1_point.rb +50 -0
  20. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/range.rb +93 -0
  21. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/specialized_string.rb +15 -0
  22. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/type_map_initializer.rb +109 -0
  23. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/uuid.rb +21 -0
  24. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/vector.rb +26 -0
  25. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid/xml.rb +28 -0
  26. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/oid.rb +31 -0
  27. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/quoting.rb +116 -0
  28. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/referential_integrity.rb +49 -0
  29. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/schema_definitions.rb +180 -0
  30. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/schema_dumper.rb +47 -0
  31. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/schema_statements.rb +682 -0
  32. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/type_metadata.rb +35 -0
  33. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql/utils.rb +77 -0
  34. data/lib/active_record/connection_adapters/ovirt_legacy_postgresql_adapter.rb +856 -0
  35. data/lib/{models → ovirt_metrics/models}/calendar.rb +0 -0
  36. data/lib/{models → ovirt_metrics/models}/cluster_configuration.rb +0 -0
  37. data/lib/{models → ovirt_metrics/models}/datacenter_configuration.rb +0 -0
  38. data/lib/{models → ovirt_metrics/models}/datacenter_daily_history.rb +0 -0
  39. data/lib/{models → ovirt_metrics/models}/datacenter_hourly_history.rb +0 -0
  40. data/lib/{models → ovirt_metrics/models}/datacenter_samples_history.rb +0 -0
  41. data/lib/{models → ovirt_metrics/models}/datacenter_storage_domain_map.rb +0 -0
  42. data/lib/{models → ovirt_metrics/models}/disks_vm_map.rb +0 -0
  43. data/lib/{models → ovirt_metrics/models}/enum_translator.rb +0 -0
  44. data/lib/{models → ovirt_metrics/models}/history_configuration.rb +0 -0
  45. data/lib/{models → ovirt_metrics/models}/host_configuration.rb +0 -0
  46. data/lib/{models → ovirt_metrics/models}/host_daily_history.rb +0 -0
  47. data/lib/{models → ovirt_metrics/models}/host_hourly_history.rb +0 -0
  48. data/lib/{models → ovirt_metrics/models}/host_interface_configuration.rb +0 -0
  49. data/lib/{models → ovirt_metrics/models}/host_interface_daily_history.rb +0 -0
  50. data/lib/{models → ovirt_metrics/models}/host_interface_hourly_history.rb +0 -0
  51. data/lib/{models → ovirt_metrics/models}/host_interface_samples_history.rb +0 -0
  52. data/lib/{models → ovirt_metrics/models}/host_samples_history.rb +0 -0
  53. data/lib/ovirt_metrics/models/ovirt_history.rb +12 -0
  54. data/lib/{models → ovirt_metrics/models}/period.rb +0 -0
  55. data/lib/{models → ovirt_metrics/models}/storage_domain_configuration.rb +0 -0
  56. data/lib/{models → ovirt_metrics/models}/storage_domain_daily_history.rb +0 -0
  57. data/lib/{models → ovirt_metrics/models}/storage_domain_hourly_history.rb +0 -0
  58. data/lib/{models → ovirt_metrics/models}/storage_domain_samples_history.rb +0 -0
  59. data/lib/{models → ovirt_metrics/models}/tag_details.rb +0 -0
  60. data/lib/{models → ovirt_metrics/models}/tag_relations_history.rb +0 -0
  61. data/lib/{models → ovirt_metrics/models}/vm_configuration.rb +0 -0
  62. data/lib/{models → ovirt_metrics/models}/vm_daily_history.rb +0 -0
  63. data/lib/{models → ovirt_metrics/models}/vm_disk_configuration.rb +0 -0
  64. data/lib/{models → ovirt_metrics/models}/vm_disk_daily_history.rb +0 -0
  65. data/lib/{models → ovirt_metrics/models}/vm_disk_hourly_history.rb +0 -0
  66. data/lib/{models → ovirt_metrics/models}/vm_disk_samples_history.rb +0 -0
  67. data/lib/{models → ovirt_metrics/models}/vm_disks_usage_daily_history.rb +0 -0
  68. data/lib/{models → ovirt_metrics/models}/vm_disks_usage_hourly_history.rb +0 -0
  69. data/lib/{models → ovirt_metrics/models}/vm_disks_usage_samples_history.rb +0 -0
  70. data/lib/{models → ovirt_metrics/models}/vm_hourly_history.rb +0 -0
  71. data/lib/{models → ovirt_metrics/models}/vm_interface_configuration.rb +0 -0
  72. data/lib/{models → ovirt_metrics/models}/vm_interface_daily_history.rb +0 -0
  73. data/lib/{models → ovirt_metrics/models}/vm_interface_hourly_history.rb +0 -0
  74. data/lib/{models → ovirt_metrics/models}/vm_interface_samples_history.rb +0 -0
  75. data/lib/{models → ovirt_metrics/models}/vm_samples_history.rb +0 -0
  76. data/lib/ovirt_metrics/version.rb +1 -1
  77. data/lib/ovirt_metrics.rb +11 -3
  78. metadata +114 -47
  79. data/lib/models/ovirt_history.rb +0 -24
@@ -0,0 +1,682 @@
1
+ require 'active_support/core_ext/string/strip'
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module OvirtLegacyPostgreSQL
6
+ class SchemaCreation < AbstractAdapter::SchemaCreation
7
+ private
8
+
9
+ def visit_ColumnDefinition(o)
10
+ o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array)
11
+ super
12
+ end
13
+
14
+ def add_column_options!(sql, options)
15
+ if options[:collation]
16
+ sql << " COLLATE \"#{options[:collation]}\""
17
+ end
18
+ super
19
+ end
20
+ end
21
+
22
+ module SchemaStatements
23
+ # Drops the database specified on the +name+ attribute
24
+ # and creates it again using the provided +options+.
25
+ def recreate_database(name, options = {}) #:nodoc:
26
+ drop_database(name)
27
+ create_database(name, options)
28
+ end
29
+
30
+ # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
31
+ # <tt>:encoding</tt> (defaults to utf8), <tt>:collation</tt>, <tt>:ctype</tt>,
32
+ # <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
33
+ # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
34
+ #
35
+ # Example:
36
+ # create_database config[:database], config
37
+ # create_database 'foo_development', encoding: 'unicode'
38
+ def create_database(name, options = {})
39
+ options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
40
+
41
+ option_string = options.inject("") do |memo, (key, value)|
42
+ memo += case key
43
+ when :owner
44
+ " OWNER = \"#{value}\""
45
+ when :template
46
+ " TEMPLATE = \"#{value}\""
47
+ when :encoding
48
+ " ENCODING = '#{value}'"
49
+ when :collation
50
+ " LC_COLLATE = '#{value}'"
51
+ when :ctype
52
+ " LC_CTYPE = '#{value}'"
53
+ when :tablespace
54
+ " TABLESPACE = \"#{value}\""
55
+ when :connection_limit
56
+ " CONNECTION LIMIT = #{value}"
57
+ else
58
+ ""
59
+ end
60
+ end
61
+
62
+ execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
63
+ end
64
+
65
+ # Drops a PostgreSQL database.
66
+ #
67
+ # Example:
68
+ # drop_database 'matt_development'
69
+ def drop_database(name) #:nodoc:
70
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
71
+ end
72
+
73
+ # Returns the list of all tables in the schema search path.
74
+ def tables(name = nil)
75
+ if name
76
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
77
+ Passing arguments to #tables is deprecated without replacement.
78
+ MSG
79
+ end
80
+
81
+ select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", 'SCHEMA')
82
+ end
83
+
84
+ def data_sources # :nodoc
85
+ select_values(<<-SQL, 'SCHEMA')
86
+ SELECT c.relname
87
+ FROM pg_class c
88
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
89
+ WHERE c.relkind IN ('r', 'v','m') -- (r)elation/table, (v)iew, (m)aterialized view
90
+ AND n.nspname = ANY (current_schemas(false))
91
+ SQL
92
+ end
93
+
94
+ # Returns true if table exists.
95
+ # If the schema is not specified as part of +name+ then it will only find tables within
96
+ # the current schema search path (regardless of permissions to access tables in other schemas)
97
+ def table_exists?(name)
98
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
99
+ #table_exists? currently checks both tables and views.
100
+ This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
101
+ Use #data_source_exists? instead.
102
+ MSG
103
+
104
+ data_source_exists?(name)
105
+ end
106
+
107
+ def data_source_exists?(name)
108
+ name = Utils.extract_schema_qualified_name(name.to_s)
109
+ return false unless name.identifier
110
+
111
+ select_value(<<-SQL, 'SCHEMA').to_i > 0
112
+ SELECT COUNT(*)
113
+ FROM pg_class c
114
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
115
+ WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
116
+ AND c.relname = '#{name.identifier}'
117
+ AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
118
+ SQL
119
+ end
120
+
121
+ def views # :nodoc:
122
+ select_values(<<-SQL, 'SCHEMA')
123
+ SELECT c.relname
124
+ FROM pg_class c
125
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
126
+ WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
127
+ AND n.nspname = ANY (current_schemas(false))
128
+ SQL
129
+ end
130
+
131
+ def view_exists?(view_name) # :nodoc:
132
+ name = Utils.extract_schema_qualified_name(view_name.to_s)
133
+ return false unless name.identifier
134
+
135
+ select_values(<<-SQL, 'SCHEMA').any?
136
+ SELECT c.relname
137
+ FROM pg_class c
138
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
139
+ WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
140
+ AND c.relname = '#{name.identifier}'
141
+ AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
142
+ SQL
143
+ end
144
+
145
+ def drop_table(table_name, options = {}) # :nodoc:
146
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
147
+ end
148
+
149
+ # Returns true if schema exists.
150
+ def schema_exists?(name)
151
+ select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = '#{name}'", 'SCHEMA').to_i > 0
152
+ end
153
+
154
+ # Verifies existence of an index with a given name.
155
+ def index_name_exists?(table_name, index_name, default)
156
+ table = Utils.extract_schema_qualified_name(table_name.to_s)
157
+ index = Utils.extract_schema_qualified_name(index_name.to_s)
158
+
159
+ select_value(<<-SQL, 'SCHEMA').to_i > 0
160
+ SELECT COUNT(*)
161
+ FROM pg_class t
162
+ INNER JOIN pg_index d ON t.oid = d.indrelid
163
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
164
+ LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
165
+ WHERE i.relkind = 'i'
166
+ AND i.relname = '#{index.identifier}'
167
+ AND t.relname = '#{table.identifier}'
168
+ AND n.nspname = #{index.schema ? "'#{index.schema}'" : 'ANY (current_schemas(false))'}
169
+ SQL
170
+ end
171
+
172
+ # Returns an array of indexes for the given table.
173
+ def indexes(table_name, name = nil)
174
+ table = Utils.extract_schema_qualified_name(table_name.to_s)
175
+
176
+ result = query(<<-SQL, 'SCHEMA')
177
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
178
+ pg_catalog.obj_description(i.oid, 'pg_class') AS comment,
179
+ (SELECT COUNT(*) FROM pg_opclass o
180
+ JOIN (SELECT unnest(string_to_array(d.indclass::text, ' '))::int oid) c
181
+ ON o.oid = c.oid WHERE o.opcdefault = 'f')
182
+ FROM pg_class t
183
+ INNER JOIN pg_index d ON t.oid = d.indrelid
184
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
185
+ LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
186
+ WHERE i.relkind = 'i'
187
+ AND d.indisprimary = 'f'
188
+ AND t.relname = '#{table.identifier}'
189
+ AND n.nspname = #{table.schema ? "'#{table.schema}'" : 'ANY (current_schemas(false))'}
190
+ ORDER BY i.relname
191
+ SQL
192
+
193
+ result.map do |row|
194
+ index_name = row[0]
195
+ unique = row[1]
196
+ indkey = row[2].split(" ").map(&:to_i)
197
+ inddef = row[3]
198
+ oid = row[4]
199
+ comment = row[5]
200
+ opclass = row[6]
201
+
202
+ using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/).flatten
203
+
204
+ if indkey.include?(0) || opclass > 0
205
+ columns = expressions
206
+ else
207
+ columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact
208
+ SELECT a.attnum, a.attname
209
+ FROM pg_attribute a
210
+ WHERE a.attrelid = #{oid}
211
+ AND a.attnum IN (#{indkey.join(",")})
212
+ SQL
213
+
214
+ # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
215
+ orders = Hash[
216
+ expressions.scan(/(\w+) DESC/).flatten.map { |order_column| [order_column, :desc] }
217
+ ]
218
+ end
219
+
220
+ IndexDefinition.new(table_name, index_name, unique, columns, [], orders, where, nil, using.to_sym, comment)
221
+ end.compact
222
+ end
223
+
224
+ # Returns the list of all column definitions for a table.
225
+ def columns(table_name) # :nodoc:
226
+ table_name = table_name.to_s
227
+ column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation, comment|
228
+ oid = oid.to_i
229
+ fmod = fmod.to_i
230
+ type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
231
+ default_value = extract_value_from_default(default)
232
+ default_function = extract_default_function(default_value, default)
233
+ new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function, collation, comment: comment.presence)
234
+ end
235
+ end
236
+
237
+ def new_column(*args) # :nodoc:
238
+ OvirtLegacyPostgreSQLColumn.new(*args)
239
+ end
240
+
241
+ # Returns a comment stored in database for given table
242
+ def table_comment(table_name) # :nodoc:
243
+ name = Utils.extract_schema_qualified_name(table_name.to_s)
244
+ if name.identifier
245
+ select_value(<<-SQL.strip_heredoc, 'SCHEMA')
246
+ SELECT pg_catalog.obj_description(c.oid, 'pg_class')
247
+ FROM pg_catalog.pg_class c
248
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
249
+ WHERE c.relname = #{quote(name.identifier)}
250
+ AND c.relkind IN ('r') -- (r)elation/table
251
+ AND n.nspname = #{name.schema ? quote(name.schema) : 'ANY (current_schemas(false))'}
252
+ SQL
253
+ end
254
+ end
255
+
256
+ # Returns the current database name.
257
+ def current_database
258
+ select_value('select current_database()', 'SCHEMA')
259
+ end
260
+
261
+ # Returns the current schema name.
262
+ def current_schema
263
+ select_value('SELECT current_schema', 'SCHEMA')
264
+ end
265
+
266
+ # Returns the current database encoding format.
267
+ def encoding
268
+ select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
269
+ end
270
+
271
+ # Returns the current database collation.
272
+ def collation
273
+ select_value("SELECT datcollate FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
274
+ end
275
+
276
+ # Returns the current database ctype.
277
+ def ctype
278
+ select_value("SELECT datctype FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
279
+ end
280
+
281
+ # Returns an array of schema names.
282
+ def schema_names
283
+ select_values(<<-SQL, 'SCHEMA')
284
+ SELECT nspname
285
+ FROM pg_namespace
286
+ WHERE nspname !~ '^pg_.*'
287
+ AND nspname NOT IN ('information_schema')
288
+ ORDER by nspname;
289
+ SQL
290
+ end
291
+
292
+ # Creates a schema for the given schema name.
293
+ def create_schema schema_name
294
+ execute "CREATE SCHEMA #{quote_schema_name(schema_name)}"
295
+ end
296
+
297
+ # Drops the schema for the given schema name.
298
+ def drop_schema(schema_name, options = {})
299
+ execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
300
+ end
301
+
302
+ # Sets the schema search path to a string of comma-separated schema names.
303
+ # Names beginning with $ have to be quoted (e.g. $user => '$user').
304
+ # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
305
+ #
306
+ # This should be not be called manually but set in database.yml.
307
+ def schema_search_path=(schema_csv)
308
+ if schema_csv
309
+ execute("SET search_path TO #{schema_csv}", 'SCHEMA')
310
+ @schema_search_path = schema_csv
311
+ end
312
+ end
313
+
314
+ # Returns the active schema search path.
315
+ def schema_search_path
316
+ @schema_search_path ||= select_value('SHOW search_path', 'SCHEMA')
317
+ end
318
+
319
+ # Returns the current client message level.
320
+ def client_min_messages
321
+ select_value('SHOW client_min_messages', 'SCHEMA')
322
+ end
323
+
324
+ # Set the client message level.
325
+ def client_min_messages=(level)
326
+ execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
327
+ end
328
+
329
+ # Returns the sequence name for a table's primary key or some other specified key.
330
+ def default_sequence_name(table_name, pk = nil) #:nodoc:
331
+ result = serial_sequence(table_name, pk || 'id')
332
+ return nil unless result
333
+ Utils.extract_schema_qualified_name(result).to_s
334
+ rescue ActiveRecord::StatementInvalid
335
+ OvirtLegacyPostgreSQL::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s
336
+ end
337
+
338
+ def serial_sequence(table, column)
339
+ select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", 'SCHEMA')
340
+ end
341
+
342
+ # Sets the sequence of a table's primary key to the specified value.
343
+ def set_pk_sequence!(table, value) #:nodoc:
344
+ pk, sequence = pk_and_sequence_for(table)
345
+
346
+ if pk
347
+ if sequence
348
+ quoted_sequence = quote_table_name(sequence)
349
+
350
+ select_value("SELECT setval('#{quoted_sequence}', #{value})", 'SCHEMA')
351
+ else
352
+ @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
353
+ end
354
+ end
355
+ end
356
+
357
+ # Resets the sequence of a table's primary key to the maximum value.
358
+ def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
359
+ unless pk and sequence
360
+ default_pk, default_sequence = pk_and_sequence_for(table)
361
+
362
+ pk ||= default_pk
363
+ sequence ||= default_sequence
364
+ end
365
+
366
+ if @logger && pk && !sequence
367
+ @logger.warn "#{table} has primary key #{pk} with no default sequence."
368
+ end
369
+
370
+ if pk && sequence
371
+ quoted_sequence = quote_table_name(sequence)
372
+
373
+ select_value(<<-end_sql, 'SCHEMA')
374
+ SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
375
+ end_sql
376
+ end
377
+ end
378
+
379
+ # Returns a table's primary key and belonging sequence.
380
+ def pk_and_sequence_for(table) #:nodoc:
381
+ # First try looking for a sequence with a dependency on the
382
+ # given table's primary key.
383
+ result = query(<<-end_sql, 'SCHEMA')[0]
384
+ SELECT attr.attname, nsp.nspname, seq.relname
385
+ FROM pg_class seq,
386
+ pg_attribute attr,
387
+ pg_depend dep,
388
+ pg_constraint cons,
389
+ pg_namespace nsp
390
+ WHERE seq.oid = dep.objid
391
+ AND seq.relkind = 'S'
392
+ AND attr.attrelid = dep.refobjid
393
+ AND attr.attnum = dep.refobjsubid
394
+ AND attr.attrelid = cons.conrelid
395
+ AND attr.attnum = cons.conkey[1]
396
+ AND seq.relnamespace = nsp.oid
397
+ AND cons.contype = 'p'
398
+ AND dep.classid = 'pg_class'::regclass
399
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
400
+ end_sql
401
+
402
+ if result.nil? or result.empty?
403
+ result = query(<<-end_sql, 'SCHEMA')[0]
404
+ SELECT attr.attname, nsp.nspname,
405
+ CASE
406
+ WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
407
+ WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
408
+ substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
409
+ strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
410
+ ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
411
+ END
412
+ FROM pg_class t
413
+ JOIN pg_attribute attr ON (t.oid = attrelid)
414
+ JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
415
+ JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
416
+ JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
417
+ WHERE t.oid = '#{quote_table_name(table)}'::regclass
418
+ AND cons.contype = 'p'
419
+ AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
420
+ end_sql
421
+ end
422
+
423
+ pk = result.shift
424
+ if result.last
425
+ [pk, OvirtLegacyPostgreSQL::Name.new(*result)]
426
+ else
427
+ [pk, nil]
428
+ end
429
+ rescue
430
+ nil
431
+ end
432
+
433
+ def primary_keys(table_name) # :nodoc:
434
+ select_values(<<-SQL.strip_heredoc, 'SCHEMA')
435
+ WITH pk_constraint AS (
436
+ SELECT conrelid, unnest(conkey) AS connum FROM pg_constraint
437
+ WHERE contype = 'p'
438
+ AND conrelid = '#{quote_table_name(table_name)}'::regclass
439
+ ), cons AS (
440
+ SELECT conrelid, connum, row_number() OVER() AS rownum FROM pk_constraint
441
+ )
442
+ SELECT attr.attname FROM pg_attribute attr
443
+ INNER JOIN cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.connum
444
+ ORDER BY cons.rownum
445
+ SQL
446
+ end
447
+
448
+ # Renames a table.
449
+ # Also renames a table's primary key sequence if the sequence name exists and
450
+ # matches the Active Record default.
451
+ #
452
+ # Example:
453
+ # rename_table('octopuses', 'octopi')
454
+ def rename_table(table_name, new_name)
455
+ clear_cache!
456
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
457
+ pk, seq = pk_and_sequence_for(new_name)
458
+ if seq && seq.identifier == "#{table_name}_#{pk}_seq"
459
+ new_seq = "#{new_name}_#{pk}_seq"
460
+ idx = "#{table_name}_pkey"
461
+ new_idx = "#{new_name}_pkey"
462
+ execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
463
+ execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
464
+ end
465
+
466
+ rename_table_indexes(table_name, new_name)
467
+ end
468
+
469
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
470
+ clear_cache!
471
+ super
472
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
473
+ end
474
+
475
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
476
+ clear_cache!
477
+ quoted_table_name = quote_table_name(table_name)
478
+ quoted_column_name = quote_column_name(column_name)
479
+ sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale], options[:array])
480
+ sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}"
481
+ if options[:collation]
482
+ sql << " COLLATE \"#{options[:collation]}\""
483
+ end
484
+ if options[:using]
485
+ sql << " USING #{options[:using]}"
486
+ elsif options[:cast_as]
487
+ cast_as_type = type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale], options[:array])
488
+ sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
489
+ end
490
+ execute sql
491
+
492
+ change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
493
+ change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
494
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
495
+ end
496
+
497
+ # Changes the default value of a table column.
498
+ def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
499
+ clear_cache!
500
+ column = column_for(table_name, column_name)
501
+ return unless column
502
+
503
+ default = extract_new_default_value(default_or_changes)
504
+ alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
505
+ if default.nil?
506
+ # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
507
+ # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
508
+ execute alter_column_query % "DROP DEFAULT"
509
+ else
510
+ execute alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
511
+ end
512
+ end
513
+
514
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
515
+ clear_cache!
516
+ unless null || default.nil?
517
+ column = column_for(table_name, column_name)
518
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
519
+ end
520
+ execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
521
+ end
522
+
523
+ # Adds comment for given table column or drops it if +comment+ is a +nil+
524
+ def change_column_comment(table_name, column_name, comment) # :nodoc:
525
+ clear_cache!
526
+ execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment)}"
527
+ end
528
+
529
+ # Adds comment for given table or drops it if +comment+ is a +nil+
530
+ def change_table_comment(table_name, comment) # :nodoc:
531
+ clear_cache!
532
+ execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}"
533
+ end
534
+
535
+ # Renames a column in a table.
536
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
537
+ clear_cache!
538
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
539
+ rename_column_indexes(table_name, column_name, new_column_name)
540
+ end
541
+
542
+ def add_index(table_name, column_name, options = {}) #:nodoc:
543
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
544
+ execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}").tap do
545
+ execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
546
+ end
547
+ end
548
+
549
+ def remove_index(table_name, options = {}) #:nodoc:
550
+ table = Utils.extract_schema_qualified_name(table_name.to_s)
551
+
552
+ if options.is_a?(Hash) && options.key?(:name)
553
+ provided_index = Utils.extract_schema_qualified_name(options[:name].to_s)
554
+
555
+ options[:name] = provided_index.identifier
556
+ table = OvirtLegacyPostgreSQL::Name.new(provided_index.schema, table.identifier) unless table.schema.present?
557
+
558
+ if provided_index.schema.present? && table.schema != provided_index.schema
559
+ raise ArgumentError.new("Index schema '#{provided_index.schema}' does not match table schema '#{table.schema}'")
560
+ end
561
+ end
562
+
563
+ index_to_remove = OvirtLegacyPostgreSQL::Name.new(table.schema, index_name_for_remove(table.to_s, options))
564
+ algorithm =
565
+ if options.is_a?(Hash) && options.key?(:algorithm)
566
+ index_algorithms.fetch(options[:algorithm]) do
567
+ raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
568
+ end
569
+ end
570
+ execute "DROP INDEX #{algorithm} #{quote_table_name(index_to_remove)}"
571
+ end
572
+
573
+ # Renames an index of a table. Raises error if length of new
574
+ # index name is greater than allowed limit.
575
+ def rename_index(table_name, old_name, new_name)
576
+ validate_index_length!(table_name, new_name)
577
+
578
+ execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
579
+ end
580
+
581
+ def foreign_keys(table_name)
582
+ fk_info = select_all <<-SQL.strip_heredoc
583
+ SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
584
+ FROM pg_constraint c
585
+ JOIN pg_class t1 ON c.conrelid = t1.oid
586
+ JOIN pg_class t2 ON c.confrelid = t2.oid
587
+ JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
588
+ JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
589
+ JOIN pg_namespace t3 ON c.connamespace = t3.oid
590
+ WHERE c.contype = 'f'
591
+ AND t1.relname = #{quote(table_name)}
592
+ AND t3.nspname = ANY (current_schemas(false))
593
+ ORDER BY c.conname
594
+ SQL
595
+
596
+ fk_info.map do |row|
597
+ options = {
598
+ column: row['column'],
599
+ name: row['name'],
600
+ primary_key: row['primary_key']
601
+ }
602
+
603
+ options[:on_delete] = extract_foreign_key_action(row['on_delete'])
604
+ options[:on_update] = extract_foreign_key_action(row['on_update'])
605
+
606
+ ForeignKeyDefinition.new(table_name, row['to_table'], options)
607
+ end
608
+ end
609
+
610
+ def extract_foreign_key_action(specifier) # :nodoc:
611
+ case specifier
612
+ when 'c'; :cascade
613
+ when 'n'; :nullify
614
+ when 'r'; :restrict
615
+ end
616
+ end
617
+
618
+ def index_name_length
619
+ 63
620
+ end
621
+
622
+ # Maps logical Rails types to PostgreSQL-specific data types.
623
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil, array = nil)
624
+ sql = case type.to_s
625
+ when 'binary'
626
+ # PostgreSQL doesn't support limits on binary (bytea) columns.
627
+ # The hard limit is 1GB, because of a 32-bit size field, and TOAST.
628
+ case limit
629
+ when nil, 0..0x3fffffff; super(type)
630
+ else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
631
+ end
632
+ when 'text'
633
+ # PostgreSQL doesn't support limits on text columns.
634
+ # The hard limit is 1GB, according to section 8.3 in the manual.
635
+ case limit
636
+ when nil, 0..0x3fffffff; super(type)
637
+ else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
638
+ end
639
+ when 'integer'
640
+ case limit
641
+ when 1, 2; 'smallint'
642
+ when nil, 3, 4; 'integer'
643
+ when 5..8; 'bigint'
644
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
645
+ end
646
+ else
647
+ super(type, limit, precision, scale)
648
+ end
649
+
650
+ sql << '[]' if array && type != :primary_key
651
+ sql
652
+ end
653
+
654
+ # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
655
+ # requires that the ORDER BY include the distinct column.
656
+ def columns_for_distinct(columns, orders) #:nodoc:
657
+ order_columns = orders.reject(&:blank?).map{ |s|
658
+ # Convert Arel node to string
659
+ s = s.to_sql unless s.is_a?(String)
660
+ # Remove any ASC/DESC modifiers
661
+ s.gsub(/\s+(?:ASC|DESC)\b/i, '')
662
+ .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '')
663
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
664
+
665
+ [super, *order_columns].join(', ')
666
+ end
667
+
668
+ def fetch_type_metadata(column_name, sql_type, oid, fmod)
669
+ cast_type = get_oid_type(oid, fmod, column_name, sql_type)
670
+ simple_type = SqlTypeMetadata.new(
671
+ sql_type: sql_type,
672
+ type: cast_type.type,
673
+ limit: cast_type.limit,
674
+ precision: cast_type.precision,
675
+ scale: cast_type.scale,
676
+ )
677
+ OvirtLegacyPostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
678
+ end
679
+ end
680
+ end
681
+ end
682
+ end