Class: DynamicMigrations::Postgres::Server::Database::Schema::Table

Inherits:
DynamicMigrations::Postgres::Server::Database::Source show all
Includes:
Columns, ForeignKeyConstraints, Indexes, Triggers, UniqueConstraints, Validations
Defined in:
lib/dynamic_migrations/postgres/server/database/schema/table.rb,
lib/dynamic_migrations/postgres/server/database/schema/table/index.rb,
lib/dynamic_migrations/postgres/server/database/schema/table/column.rb,
lib/dynamic_migrations/postgres/server/database/schema/table/columns.rb,
lib/dynamic_migrations/postgres/server/database/schema/table/indexes.rb,
lib/dynamic_migrations/postgres/server/database/schema/table/trigger.rb,
lib/dynamic_migrations/postgres/server/database/schema/table/triggers.rb,
lib/dynamic_migrations/postgres/server/database/schema/table/validation.rb,
lib/dynamic_migrations/postgres/server/database/schema/table/primary_key.rb,
lib/dynamic_migrations/postgres/server/database/schema/table/validations.rb,
lib/dynamic_migrations/postgres/server/database/schema/table/unique_constraint.rb,
lib/dynamic_migrations/postgres/server/database/schema/table/unique_constraints.rb,
lib/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraint.rb,
lib/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraints.rb

Overview

This class represents a postgres table.

Defined Under Namespace

Modules: Columns, ForeignKeyConstraints, Indexes, Triggers, UniqueConstraints, Validations Classes: Column, ExpectedSchemaError, ForeignKeyConstraint, Index, MissingExtensionError, PrimaryKey, PrimaryKeyAlreadyExistsError, PrimaryKeyDoesNotExistError, Trigger, UniqueConstraint, Validation

Instance Attribute Summary collapse

Attributes inherited from DynamicMigrations::Postgres::Server::Database::Source

#source

Instance Method Summary collapse

Methods included from UniqueConstraints

#add_unique_constraint, #has_unique_constraint?, #unique_constraint, #unique_constraints, #unique_constraints_hash

Methods included from Triggers

#add_trigger, #has_trigger?, #trigger, #triggers, #triggers_hash

Methods included from ForeignKeyConstraints

#add_foreign_key_constraint, #add_remote_foreign_key_constraint, #foreign_key_constraint, #foreign_key_constraints, #foreign_key_constraints_hash, #has_foreign_key_constraint?

Methods included from Indexes

#add_index, #has_index?, #index, #indexes, #indexes_hash

Methods included from Validations

#add_validation, #has_validation?, #validation, #validations, #validations_hash

Methods included from Columns

#add_column, #column, #columns, #columns_hash, #has_column?

Methods inherited from DynamicMigrations::Postgres::Server::Database::Source

#assert_is_a_symbol!, #from_configuration?, #from_database?

Constructor Details

#initialize(source, schema, name, description: nil) ⇒ Table

initialize a new object to represent a postgres table



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/dynamic_migrations/postgres/server/database/schema/table.rb', line 35

def initialize source, schema, name, description: nil
  super source

  raise ExpectedSchemaError, schema unless schema.is_a? Schema
  @schema = schema

  raise ExpectedSymbolError, name unless name.is_a? Symbol
  @name = name

  unless description.nil?
    raise ExpectedStringError, description unless description.is_a? String
    @description = description.strip
    @description = nil if description == ""
  end

  @columns = {}
  @validations = {}
  @indexes = {}
  @foreign_key_constraints = {}
  @remote_foreign_key_constraints = []
  @triggers = {}
  @unique_constraints = {}
end

Instance Attribute Details

#descriptionObject (readonly)

Returns the value of attribute description.



31
32
33
# File 'lib/dynamic_migrations/postgres/server/database/schema/table.rb', line 31

def description
  @description
end

#nameObject (readonly)

Returns the value of attribute name.



30
31
32
# File 'lib/dynamic_migrations/postgres/server/database/schema/table.rb', line 30

def name
  @name
end

#remote_foreign_key_constraintsObject (readonly)

Returns the value of attribute remote_foreign_key_constraints.



32
33
34
# File 'lib/dynamic_migrations/postgres/server/database/schema/table.rb', line 32

def remote_foreign_key_constraints
  @remote_foreign_key_constraints
end

#schemaObject (readonly)

Returns the value of attribute schema.



29
30
31
# File 'lib/dynamic_migrations/postgres/server/database/schema/table.rb', line 29

def schema
  @schema
end

Instance Method Details

#add_primary_key(name, column_names, **primary_key_options) ⇒ Object

add a primary key to this table



65
66
67
68
69
# File 'lib/dynamic_migrations/postgres/server/database/schema/table.rb', line 65

def add_primary_key name, column_names, **primary_key_options
  raise PrimaryKeyAlreadyExistsError if @primary_key
  columns = column_names.map { |column_name| column column_name }
  @primary_key = PrimaryKey.new source, self, columns, name, **primary_key_options
end

#create_temp_table(connection, temp_table_name) ⇒ Object

Used within validations and triggers when normalizing check clauses and other SQL statements which require a table to process the SQL.

This method returns a hash representation of any temporary enums created to satisfy the columns in the table



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/dynamic_migrations/postgres/server/database/schema/table.rb', line 90

def create_temp_table connection, temp_table_name
  # create the temp table and add the expected columns

  # if any of the columns are enums, then we need to create a temporary enum type for them.
  # we cant just create temporary columns as text fields because postgres may automatically
  # add casts to those columns, which would result in a different normalized check clause
  temp_enums = {}

  # an array of sql column definitions for within the create table SQL
  # we process each column individually like this so that we can create temporary enums for
  # any enum columns
  columns_sql = columns.map do |column|
    enum = column.enum
    if enum
      # create the temporary enum type
      temp_enum_name = "#{temp_table_name}_enum_#{temp_enums.count}"
      connection.exec(<<~SQL)
        CREATE TYPE #{temp_enum_name} as ENUM ('#{enum.values.join("','")}');
      SQL
      temp_enums[temp_enum_name] = enum

      # return the column definition used within the CREATE TABLE SQL
      data_type = column.array? ? "#{temp_enum_name}[]" : temp_enum_name
      "\"#{column.name}\" #{data_type}"

    else
      # return the column definition used within the CREATE TABLE SQL
      "\"#{column.name}\" #{column.data_type}"
    end
  end

  # in case any of the columnbs are citext columns
  # in case any of the columns use the citext data type
  required_extensions = []
  if columns.any? { |column| column.data_type.start_with? "citext" }
    required_extensions << "citext"
  end
  if columns.any? { |column| column.data_type.start_with? "postgis" }
    required_extensions << "postgis"
  end

  required_extensions.each do |extension_name|
    extension_result = connection.exec(<<~SQL)
      SELECT
        (
          SELECT 1
          FROM pg_available_extensions
          WHERE name = '#{extension_name}'
        ) as is_available,
        (
          SELECT 1
          FROM pg_extension
          WHERE extname = '#{extension_name}'
        ) as is_installed
    SQL

    row = extension_result.first
    raise MissingExtensionError, "unexpected error" if row.nil?

    unless row["is_installed"]
      detail = if row["is_available"]
        <<~DETAIL
          The `#{extension_name}` extension is available for installation,
          but has not been installed for this database.
        DETAIL
      else
        <<~DETAIL
          The `#{extension_name}` extension is not installed, and does not
          appear to be available for installation.
        DETAIL
      end
      raise MissingExtensionError, <<~ERROR.tr!("\n", " ")
        This table uses the `#{extension_name}` data type. #{detail}
        Add the extension, then generate and run the migrations which will
        enable the extension for your database before defining validations
        or triggers which rely on it.

        Note, the `#{extension_name}` extension is required even for defining
        some validations and triggers. This library needs to connect to postgres
        and gererate normalized versions of validation check clauses and trigger
        action conditions before it can even compare them to validations or triggers
        which may or may not already exist in the database.
      ERROR
    end
  end

  # if any of the columns require postgis
  if required_extensions.include? "postgis"
    connection.exec("SET search_path TO public,postgis;")
  end

  # note, this is not actually a TEMP TABLE, it is created within a transaction
  # and rolled back.
  connection.exec(<<~SQL)
    CREATE TABLE #{temp_table_name} (
      #{columns_sql.join(", ")}
    );
  SQL

  temp_enums
end

#has_description?Boolean

returns true if this table has a description, otehrwise false

Returns:

  • (Boolean)


60
61
62
# File 'lib/dynamic_migrations/postgres/server/database/schema/table.rb', line 60

def has_description?
  !@description.nil?
end

#has_primary_key?Boolean

returns true if this table has a primary key, otherwise false

Returns:

  • (Boolean)


72
73
74
# File 'lib/dynamic_migrations/postgres/server/database/schema/table.rb', line 72

def has_primary_key?
  !@primary_key.nil?
end

#primary_keyObject

returns a primary key if one exists, else raises an error



77
78
79
80
81
82
83
# File 'lib/dynamic_migrations/postgres/server/database/schema/table.rb', line 77

def primary_key
  pk = @primary_key
  unless pk
    raise PrimaryKeyDoesNotExistError
  end
  pk
end