Module: ActiveRecord::ConnectionAdapters::Materialize::SchemaStatements

Included in:
ActiveRecord::ConnectionAdapters::MaterializeAdapter
Defined in:
lib/active_record/connection_adapters/materialize/schema_statements.rb

Instance Method Summary collapse

Instance Method Details

#add_column(table_name, column_name, type, **options) ⇒ Object



179
180
181
182
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 179

def add_column(table_name, column_name, type, **options) #:nodoc:
  clear_cache!
  super
end

#add_index(table_name, column_name, options = {}) ⇒ Object

:nodoc:



214
215
216
217
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 214

def add_index(table_name, column_name, options = {}) #:nodoc:
  index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, **options)
  execute("CREATE INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns_and_opclasses})")
end

#change_column(table_name, column_name, type, options = {}) ⇒ Object



185
186
187
188
189
190
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 185

def change_column(table_name, column_name, type, options = {}) #:nodoc:
  clear_cache!
  sqls, procs = Array(change_column_for_alter(table_name, column_name, type, options)).partition { |v| v.is_a?(String) }
  execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
  procs.each(&:call)
end

#change_column_default(table_name, column_name, default_or_changes) ⇒ Object



193
194
195
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 193

def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
  execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
end

#change_column_null(table_name, column_name, null, default = nil) ⇒ Object



198
199
200
201
202
203
204
205
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 198

def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
  clear_cache!
  unless null || default.nil?
    column = column_for(table_name, column_name)
    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
  end
  execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_null_for_alter(table_name, column_name, null, default)}"
end

#collationObject

Unsupported collation



114
115
116
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 114

def collation
  nil
end

#columns_for_distinct(columns, orders) ⇒ Object

TODO: verify same functionality as postgres Materialize requires the ORDER BY columns in the select list for distinct queries, and requires that the ORDER BY include the distinct column.



264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 264

def columns_for_distinct(columns, orders) #:nodoc:
  order_columns = orders.reject(&:blank?).map { |s|
    # Convert Arel node to string
    s = visitor.compile(s) unless s.is_a?(String)
    # Remove any ASC/DESC modifiers
    s
      .gsub(/\s+(?:ASC|DESC)\b/i, "")
      .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
  }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }

  (order_columns << super).join(", ")
end

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

Create a new Materialize database. Options include :owner, :template, :encoding (defaults to utf8), :collation, :ctype, :tablespace, and :connection_limit (note that MySQL uses :charset while Materialize uses :encoding).

Example:

create_database config[:database], config
create_database 'foo_development', encoding: 'unicode'


22
23
24
25
26
27
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 22

def create_database(name, options = {})
  options = {}.merge(options.symbolize_keys)
  if_exists = "IF NOT EXISTS " unless [false, 'false', '0', 0].include? options[:if_exists]

  execute "CREATE DATABASE #{if_exists}#{quote_table_name(name)}"
end

#create_schema(schema_name) ⇒ Object

Creates a schema for the given schema name.



136
137
138
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 136

def create_schema(schema_name)
  execute "CREATE SCHEMA #{quote_schema_name(schema_name.to_s)}"
end

#create_schema_dumper(options) ⇒ Object

:nodoc:



281
282
283
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 281

def create_schema_dumper(options) # :nodoc:
  Materialize::SchemaDumper.create(self, options)
end

#ctypeObject

Get ctype



119
120
121
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 119

def ctype
  query_value("SELECT datctype FROM pg_database WHERE datname = #{quote(current_database)}", "SCHEMA")
end

#current_databaseObject

Unsupported current_database() use default config



99
100
101
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 99

def current_database
  @config[:database]
end

#current_schemaObject

Returns the current schema name.



104
105
106
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 104

def current_schema
  query_value("SELECT current_schema", "SCHEMA")
end

#default_sequence_name(table_name, pk = "id") ⇒ Object

Returns the sequence name for compatability



151
152
153
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 151

def default_sequence_name(table_name, pk = "id") #:nodoc:
  Materialize::Name.new(nil, "#{table_name}_#{pk}_seq").to_s
end

#drop_database(name) ⇒ Object

Drops a Materialize database.

Example:

drop_database 'matt_development'


33
34
35
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 33

def drop_database(name) #:nodoc:
  execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
end

#drop_schema(schema_name, options = {}) ⇒ Object

Drops the schema for the given schema name.



141
142
143
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 141

def drop_schema(schema_name, options = {})
  execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name.to_s)} CASCADE"
end

#drop_table(table_name, options = {}) ⇒ Object

:nodoc:



37
38
39
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 37

def drop_table(table_name, options = {}) # :nodoc:
  execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end

#encodingObject

Get encoding of database



109
110
111
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 109

def encoding
  query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = #{quote(current_database)}", "SCHEMA")
end

#index_name_exists?(table_name, index_name) ⇒ Boolean

Verifies existence of an index with a given name.

Returns:

  • (Boolean)


47
48
49
50
51
52
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 47

def index_name_exists?(table_name, index_name)
  available_indexes = execute(<<~SQL, "SCHEMA")
    SHOW INDEXES FROM #{quote_table_name(table_name)}
  SQL
  available_indexes.map { |c| c['key_name'] }.include? index_name.to_s
end

#indexes(table_name) ⇒ Object

Returns an array of indexes for the given table.



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 55

def indexes(table_name) # :nodoc:
  available_indexes = execute(<<~SQL, "SCHEMA")
    SHOW INDEXES FROM #{quote_table_name(table_name)}
  SQL
  available_indexes.group_by { |c| c['key_name'] }.map do |k, v|
    index_name = k
    IndexDefinition.new(
      table_name,
      index_name,
      unique = false,
      columns = v.map { |c| c['column_name'] },
      lengths: {},
      orders: {},
      opclasses: {},
      where: nil,
      type: nil,
      using: nil,
      comment: nil
    )
  end
end

#primary_keys(table_name) ⇒ Object

Primary keys - approximation



156
157
158
159
160
161
162
163
164
165
166
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 156

def primary_keys(table_name) # :nodoc:
  available_indexes = execute(<<~SQL, "SCHEMA").group_by { |c| c['key_name'] }
    SHOW INDEXES FROM #{quote_table_name(table_name)}
  SQL
  possible_primary = available_indexes.keys.select { |c| c.include? "primary_idx" }
  if possible_primary.size == 1
    available_indexes[possible_primary.first].map { |c| c['column_name'] }
  else
    []
  end
end

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

Drops the database specified on the name attribute and creates it again using the provided options.



9
10
11
12
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 9

def recreate_database(name, options = {}) #:nodoc:
  drop_database(name)
  create_database(name, options)
end

#remove_index(table_name, options = {}) ⇒ Object

:nodoc:



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 219

def remove_index(table_name, options = {}) #:nodoc:
  table = Utils.extract_schema_qualified_name(table_name.to_s)

  if options.is_a?(Hash) && options.key?(:name)
    provided_index = Utils.extract_schema_qualified_name(options[:name].to_s)

    options[:name] = provided_index.identifier
    table = Materialize::Name.new(provided_index.schema, table.identifier) unless table.schema.present?

    if provided_index.schema.present? && table.schema != provided_index.schema
      raise ArgumentError.new("Index schema '#{provided_index.schema}' does not match table schema '#{table.schema}'")
    end
  end

  index_to_remove = Materialize::Name.new(table.schema, index_name_for_remove(table.to_s, options))
  algorithm =
    if options.is_a?(Hash) && options.key?(:algorithm)
      index_algorithms.fetch(options[:algorithm]) do
        raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
      end
    end
  execute "DROP INDEX #{algorithm} #{quote_table_name(index_to_remove)}"
end

#rename_column(table_name, column_name, new_column_name) ⇒ Object



208
209
210
211
212
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 208

def rename_column(table_name, column_name, new_column_name) #:nodoc:
  clear_cache!
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
  rename_column_indexes(table_name, column_name, new_column_name)
end

#rename_index(table_name, old_name, new_name) ⇒ Object

Renames an index of a table. Raises error if length of new index name is greater than allowed limit.



245
246
247
248
249
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 245

def rename_index(table_name, old_name, new_name)
  validate_index_length!(table_name, new_name)

  execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
end

#rename_table(table_name, new_name) ⇒ Object

Renames a table with index references



169
170
171
172
173
174
175
176
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 169

def rename_table(table_name, new_name)
  clear_cache!
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
  if index_name_exists?(new_name, "#{table_name}_primary_idx")
    rename_index(new_name, "#{table_name}_primary_idx", "#{new_name}_primary_idx")
  end
  rename_table_indexes(table_name, new_name)
end

#schema_exists?(name) ⇒ Boolean

Returns true if schema exists.

Returns:

  • (Boolean)


42
43
44
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 42

def schema_exists?(name)
  schema_names.include? name.to_s
end

#schema_namesObject

Returns an array of schema names.



124
125
126
127
128
129
130
131
132
133
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 124

def schema_names
  query_values(<<~SQL, "SCHEMA")
    SELECT
      s.name
    FROM mz_schemas s
    LEFT JOIN mz_databases d ON s.database_id = d.id
    WHERE
      d.name = #{quote(current_database)}
  SQL
end

#schema_search_pathObject

Returns the active schema search path.



146
147
148
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 146

def schema_search_path
  @schema_search_path ||= query_value("SHOW search_path", "SCHEMA")
end

#table_comment(table_name) ⇒ Object

Returns a comment stored in database for given table



84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 84

def table_comment(table_name) # :nodoc:
  scope = quoted_scope(table_name, type: "BASE TABLE")
  if scope[:name]
    query_value(<<~SQL, "SCHEMA")
      SELECT pg_catalog.obj_description(c.oid, 'pg_class')
      FROM pg_catalog.pg_class c
        LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
      WHERE c.relname = #{scope[:name]}
        AND c.relkind IN (#{scope[:type]})
        AND n.nspname = #{scope[:schema]}
    SQL
  end
end

#table_options(table_name) ⇒ Object

:nodoc:



77
78
79
80
81
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 77

def table_options(table_name) # :nodoc:
  if comment = table_comment(table_name)
    { comment: comment }
  end
end

#type_to_sql(type, limit: nil, precision: nil, scale: nil) ⇒ Object

Maps logical Rails types to Materialize-specific data types.



252
253
254
255
256
257
258
259
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 252

def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) # :nodoc:
  type = type.to_sym if type
  if native = native_database_types[type]
    (native.is_a?(Hash) ? native[:name] : native).dup
  else
    type.to_s
  end
end

#update_table_definition(table_name, base) ⇒ Object

:nodoc:



277
278
279
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 277

def update_table_definition(table_name, base) # :nodoc:
  Materialize::Table.new(table_name, base)
end

#validate_constraint(table_name, constraint_name) ⇒ Object

Validates the given constraint.

Validates the constraint named constraint_name on accounts.

validate_constraint :accounts, :constraint_name


290
291
292
293
294
295
296
297
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 290

def validate_constraint(table_name, constraint_name)
  return unless supports_validate_constraints?

  at = create_alter_table table_name
  at.validate_constraint constraint_name

  execute schema_creation.accept(at)
end

#validate_foreign_key(from_table, to_table = nil, **options) ⇒ Object

Validates the given foreign key.

Validates the foreign key on accounts.branch_id.

validate_foreign_key :accounts, :branches

Validates the foreign key on accounts.owner_id.

validate_foreign_key :accounts, column: :owner_id

Validates the foreign key named special_fk_name on the accounts table.

validate_foreign_key :accounts, name: :special_fk_name

The options hash accepts the same keys as SchemaStatements#add_foreign_key.



314
315
316
317
318
319
320
# File 'lib/active_record/connection_adapters/materialize/schema_statements.rb', line 314

def validate_foreign_key(from_table, to_table = nil, **options)
  return unless supports_validate_constraints?

  fk_name_to_validate = foreign_key_for!(from_table, to_table: to_table, **options).name

  validate_constraint from_table, fk_name_to_validate
end