Module: Torque::PostgreSQL::Adapter::SchemaStatements

Included in:
Torque::PostgreSQL::Adapter
Defined in:
lib/torque/postgresql/adapter/schema_statements.rb

Instance Method Summary collapse

Instance Method Details

#add_composite_column(type_name, column_name, type, options = {}) ⇒ Object

Adds a single column to an existing composite type



84
85
86
87
88
89
90
91
92
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 84

def add_composite_column(type_name, column_name, type, options = {})
  options = options.dup
  schema = options.delete(:schema)

  td = create_composite_type_definition(type_name)
  td.column(column_name, type, **options)

  alter_composite_type(type_name, schema, composite_type_actions(type_name, td))
end

#add_enum_values(name, values, options = {}) ⇒ Object

Changes the enumerator by adding new values

Example:

add_enum_values 'status', ['baz']
add_enum_values 'status', ['baz'], before: 'bar'
add_enum_values 'status', ['baz'], after: 'foo'
add_enum_values 'status', ['baz'], prepend: true


157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 157

def add_enum_values(name, values, options = {})
  name   = sanitize_name_with_schema(name, options)
  before = options.fetch(:before, false)
  after  = options.fetch(:after,  false)

  before = enum_values(name).first if options.key? :prepend
  before = quote(before) unless before == false
  after  = quote(after)  unless after == false

  quote_enum_values(name, values, options).each do |value|
    reference = "BEFORE #{before}" unless before == false
    reference = "AFTER  #{after}"  unless after == false
    execute <<-SQL.squish
      ALTER TYPE #{quote_type_name(name)}
      ADD VALUE #{value} #{reference}
    SQL

    before = false
    after  = value
  end
end

#add_search_language(table, name, options = {}) ⇒ Object

Creates a column that stores the underlying language of the record so that a search vector can be created dynamically based on it. It uses a regconfig type, so string conversions are mandatory



125
126
127
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 125

def add_search_language(table, name, options = {})
  add_column(table, name, :regconfig, options)
end

#add_search_vector(table, name, columns, options = {}) ⇒ Object

Creates a column and setup a search vector as a virtual column. The options are dev-friendly and controls how the vector function will be defined

Options

[:columns] The list of columns that will be used to create the search vector. It can be a single column, an array of columns, or a hash as a combination of column name and weight (A, B, C, or D). [:language] Specify the language config to be used for the search vector. If a string is provided, then the value will be statically embedded. If a symbol is provided, then it will reference another column. [:stored] Specify if the value should be stored in the database. As of now, PostgreSQL only supports true, which will create a stored column.



145
146
147
148
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 145

def add_search_vector(table, name, columns, options = {})
  options = Builder.search_vector_options(columns: columns, **options)
  add_column(table, name, options.delete(:type), options)
end

#assume_migrated_upto_version(version) ⇒ Object

Add proper support for schema load when using versioned commands



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 277

def assume_migrated_upto_version(version)
  return super unless PostgreSQL.config.versioned_commands.enabled
  return super if (commands = pool.migration_context.migration_commands).empty?

  version = version.to_i
  migration_context = pool.migration_context
  migrated = migration_context.get_all_versions
  versions = migration_context.migrations.map(&:version)

  inserting = (versions - migrated).select { |v| v < version }
  inserting << version unless migrated.include?(version)
  return if inserting.empty?

  duplicated = inserting.tally.filter_map { |v, count| v if count > 1 }
  raise <<~MSG.squish if duplicated.present?
    Duplicate migration #{duplicated.first}.
    Please renumber your migrations to resolve the conflict.
  MSG

  VersionedCommands::SchemaTable.new(pool).create_table
  execute insert_versions_sql(inserting)
end

#change_composite_column(type_name, column_name, type, options = {}) ⇒ Object

Changes the type of a single column of an existing composite type



102
103
104
105
106
107
108
109
110
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 102

def change_composite_column(type_name, column_name, type, options = {})
  options = options.dup
  schema = options.delete(:schema)

  td = create_composite_type_definition(type_name)
  td.change(column_name, type, **options)

  alter_composite_type(type_name, schema, composite_type_actions(type_name, td))
end

#change_composite_type(name, options = {}) {|td| ... } ⇒ Object

Changes an existing composite type, using the same DSL as change_table, although limited to what a type supports

Example:

change_composite_type :address do |t|
t.string 'zipcode'
t.change 'number', :bigint
t.remove 'city'
t.rename 'street', 'road'
end

Yields:

  • (td)


63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 63

def change_composite_type(name, options = {})
  td = create_composite_type_definition(name)
  yield td

  validate_composite_type!(name, td)
  type_name = quote_type_name(sanitize_name_with_schema(name, options.dup))
  actions = composite_type_actions(name, td)

  execute("ALTER TYPE #{type_name} #{actions.join(', ')}") if actions.any?

  td.renames.each do |from, to|
    execute <<~SQL.squish
      ALTER TYPE #{type_name}
      RENAME ATTRIBUTE #{quote_column_name(from)} TO #{quote_column_name(to)}
    SQL
  end

  reset_composite_type(name)
end

#create_composite_type(name, options = {}) {|td| ... } ⇒ Object

Creates a new composite type, with the columns being defined using the same DSL as create_table, although limited to the type itself, the size-related options, and no indexes or constraints

Example:

create_composite_type :address do |t|
t.string  "street"
t.integer "number"
end

Yields:

  • (td)


37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 37

def create_composite_type(name, options = {})
  td = create_composite_type_definition(name)
  yield td if block_given?

  validate_composite_type!(name, td)
  drop_type(name, force: options[:force], check: true, schema: options[:schema]) \
    if options[:force]

  columns = td.columns.map { |column| composite_column_definition(name, column) }
  type_name = sanitize_name_with_schema(name, options.dup)

  internal_exec_query(<<~SQL.squish, 'SCHEMA').tap { reload_type_map }
    CREATE TYPE #{quote_type_name(type_name)} AS (#{columns.join(', ')})
  SQL
end

#create_table(table_name, **options, &block) ⇒ Object

Spread the features of the parent tables into the one being created, since PostgreSQL only propagates columns and their check constraints



191
192
193
194
195
196
197
198
199
200
201
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 191

def create_table(table_name, **options, &block)
  sync = options.delete(:sync)
  result = super
  return result if sync.blank?

  parents = Array.wrap(options[:inherits]).flatten.compact.map(&:to_s)
  return result if parents.empty?

  sync_inheritance_into(table_name.to_s, parents, sync_inheritance_selection(sync), false)
  result
end

#data_source_sql(name = nil, type: nil) ⇒ Object

Fix the query to include the schema on tables names when dumping



245
246
247
248
249
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 245

def data_source_sql(name = nil, type: nil)
  return super unless name.nil?

  super.sub('SELECT c.relname FROM', "SELECT n.nspname || '.' || c.relname FROM")
end

#drop_type(name, options = {}) ⇒ Object

Drops a type



8
9
10
11
12
13
14
15
16
17
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 8

def drop_type(name, options = {})
  force = options.fetch(:force, '').upcase
  check = 'IF EXISTS' if options.fetch(:check, true)
  name = sanitize_name_with_schema(name, options)

  internal_exec_query(<<-SQL.squish).tap { reload_type_map }
    DROP TYPE #{check}
    #{quote_type_name(name)} #{force}
  SQL
end

#enum_values(name) ⇒ Object

Returns all values that an enum type can have.



180
181
182
183
184
185
186
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 180

def enum_values(name)
  select_values(<<-SQL.squish, 'SCHEMA')
    SELECT enumlabel FROM pg_enum
    WHERE enumtypid = #{quote(name)}::regtype::oid
    ORDER BY enumsortorder
  SQL
end

#insert_versions_sql(versions) ⇒ Object

Add proper support for schema load when using versioned commands



301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 301

def insert_versions_sql(versions)
  return super unless PostgreSQL.config.versioned_commands.enabled

  commands = pool.migration_context.migration_commands.select do |migration|
    versions.include?(migration.version)
  end

  return super if commands.empty?

  table = quote_table_name(VersionedCommands::SchemaTable.new(pool).table_name)

  sql = super(versions - commands.map(&:version))
  sql << "\nINSERT INTO #{table} (version, type, object_name) VALUES\n"
  sql << commands.map do |m|
    +"(#{quote(m.version)}, #{quote(m.type)}, #{quote(m.object_name)})"
  end.join(",\n")
  sql << ";"
  sql
end

#primary_key(table_name) ⇒ Object

A primary key that came from a parent table is described by the sync option while dumping, otherwise the dumper would describe the inherited column all over again and the load would break on the duplicate



206
207
208
209
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 206

def primary_key(table_name)
  return super unless @_dump_mode
  sync_inheritance_parent_primary_key?(table_name) ? nil : super
end

#quoted_scope(name = nil, type: nil) ⇒ Object

When dumping the schema we need to add all schemas, not only those active for the current schema_search_path



235
236
237
238
239
240
241
242
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 235

def quoted_scope(name = nil, type: nil)
  return super unless name.nil?

  scope = super
  global = scope[:schema].start_with?('ANY (')
  scope[:schema] = "ANY ('{#{user_defined_schemas.join(',')}}')"
  scope
end

#remove_composite_column(type_name, column_name, type = nil, options = {}) ⇒ Object

Removes a single column of an existing composite type. The type is only needed to make the migration reversible



96
97
98
99
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 96

def remove_composite_column(type_name, column_name, type = nil, options = {})
  action = "DROP ATTRIBUTE #{quote_column_name(column_name)}"
  alter_composite_type(type_name, options[:schema], [action])
end

#rename_composite_column(type_name, column_name, new_name, options = {}) ⇒ Object

Renames a single column of an existing composite type



113
114
115
116
117
118
119
120
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 113

def rename_composite_column(type_name, column_name, new_name, options = {})
  action = <<~SQL.squish
    RENAME ATTRIBUTE #{quote_column_name(column_name)}
    TO #{quote_column_name(new_name)}
  SQL

  alter_composite_type(type_name, options[:schema], [action])
end

#rename_type(type_name, new_name, options = {}) ⇒ Object

Renames a type



20
21
22
23
24
25
26
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 20

def rename_type(type_name, new_name, options = {})
  type_name = sanitize_name_with_schema(type_name, options)
  internal_exec_query(<<-SQL.squish).tap { reload_type_map }
    ALTER TYPE #{quote_type_name(type_name)}
    RENAME TO #{Quoting::Name.new(nil, new_name.to_s).quoted}
  SQL
end

#table_options(table_name) ⇒ Object

Add the schema option when extracting table options



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 212

def table_options(table_name)
  options = super

  if PostgreSQL.config.schemas.enabled
    table, schema = table_name.split('.').reverse
    if table.present? && schema.present? && schema != current_schema
      options[:schema] = schema
    end
  end

  if options[:options]&.start_with?('INHERITS (')
    options.delete(:options)

    tables = inherited_table_names(table_name)
    options[:inherits] = tables.one? ? tables.first : tables
    options[:sync] = { primary_key: true } if sync_inheritance_parent_primary_key?(table_name)
  end

  options
end

#type_to_sql(type, composite_type: nil, **options) ⇒ Object

Maps the composite type through its required option, mirroring how enums are handled

Raises:

  • (ArgumentError)


264
265
266
267
268
269
270
271
272
273
274
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 264

def type_to_sql(type, composite_type: nil, **options)
  return super(type, **options) unless type.to_s == 'composite'

  raise ArgumentError, <<~MSG.squish if composite_type.nil?
    composite_type is required for composite columns
  MSG

  sql = composite_type.to_s
  sql = "#{sql}[]" if options[:array]
  sql
end

#valid_column_definition_optionsObject

Add composite_type as one of the valid options for column definition



258
259
260
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 258

def valid_column_definition_options
  super + [:composite_type]
end

#valid_table_definition_optionsObject

Add schema and inherits as one of the valid options for table definition



253
254
255
# File 'lib/torque/postgresql/adapter/schema_statements.rb', line 253

def valid_table_definition_options
  super + [:schema, :inherits]
end