Module: ActiveRecord::ConnectionAdapters::CockroachDB::SchemaStatements

Includes:
PostgreSQL::SchemaStatements
Included in:
ActiveRecord::ConnectionAdapters::CockroachDBAdapter
Defined in:
lib/active_record/connection_adapters/cockroachdb/schema_statements.rb

Instance Method Summary collapse

Instance Method Details

#add_index(table_name, column_name, **options) ⇒ Object



7
8
9
10
11
12
13
14
15
# File 'lib/active_record/connection_adapters/cockroachdb/schema_statements.rb', line 7

def add_index(table_name, column_name, **options)
  super
rescue ActiveRecord::StatementInvalid => error
  if debugging? && error.cause.class == PG::FeatureNotSupported
    warn "#{error}\n\nThis error will be ignored and the index will not be created.\n\n"
  else
    raise error
  end
end

#create_schema_dumper(options) ⇒ Object



192
193
194
# File 'lib/active_record/connection_adapters/cockroachdb/schema_statements.rb', line 192

def create_schema_dumper(options)
  CockroachDB::SchemaDumper.create(self, options)
end

#create_table_definition(*args, **kwargs) ⇒ Object

override



182
183
184
# File 'lib/active_record/connection_adapters/cockroachdb/schema_statements.rb', line 182

def create_table_definition(*args, **kwargs)
  CockroachDB::TableDefinition.new(self, *args, **kwargs)
end

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

CockroachDB uses unique_rowid() for primary keys, not sequences. It's possible to force a table to use sequences, but since it's not the default behavior we'll always return nil for default_sequence_name.



73
74
75
# File 'lib/active_record/connection_adapters/cockroachdb/schema_statements.rb', line 73

def default_sequence_name(table_name, pk = "id")
  nil
end

#foreign_keys(table_name) ⇒ Object

override Modified version of the postgresql foreign_keys method. Replaces t2.oid::regclass::text with t2.relname since this is more efficient in CockroachDB.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/active_record/connection_adapters/cockroachdb/schema_statements.rb', line 39

def foreign_keys(table_name)
  scope = quoted_scope(table_name)
  fk_info = exec_query(<<~SQL, "SCHEMA")
    SELECT t2.relname 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, c.convalidated AS valid
    FROM pg_constraint c
    JOIN pg_class t1 ON c.conrelid = t1.oid
    JOIN pg_class t2 ON c.confrelid = t2.oid
    JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
    JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
    JOIN pg_namespace t3 ON c.connamespace = t3.oid
    WHERE c.contype = 'f'
      AND t1.relname = #{scope[:name]}
      AND t3.nspname = #{scope[:schema]}
    ORDER BY c.conname
  SQL

  fk_info.map do |row|
    options = {
      column: row["column"],
      name: row["name"],
      primary_key: row["primary_key"]
    }

    options[:on_delete] = extract_foreign_key_action(row["on_delete"])
    options[:on_update] = extract_foreign_key_action(row["on_update"])
    options[:validate] = row["valid"]

    ForeignKeyDefinition.new(table_name, row["to_table"], options)
  end
end

#native_database_typesObject

override



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/active_record/connection_adapters/cockroachdb/schema_statements.rb', line 165

def native_database_types
  # Add spatial types
  super.merge(
    geography:           { name: "geography" },
    geometry:            { name: "geometry" },
    geometry_collection: { name: "geometry_collection" },
    line_string:         { name: "line_string" },
    multi_line_string:   { name: "multi_line_string" },
    multi_point:         { name: "multi_point" },
    multi_polygon:       { name: "multi_polygon" },
    spatial:             { name: "geometry" },
    st_point:            { name: "st_point" },
    st_polygon:          { name: "st_polygon" }
  )
end

#new_column_from_field(table_name, field) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/active_record/connection_adapters/cockroachdb/schema_statements.rb', line 79

def new_column_from_field(table_name, field)
  column_name, type, default, notnull, oid, fmod, collation, comment, generated, hidden = field
   = (column_name, type, oid.to_i, fmod.to_i)
  default_value = extract_value_from_default(default)
  default_function = extract_default_function(default_value, default)

  serial =
    if (match = default_function&.match(/\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z/))
      sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name]
    end

  # {:dimension=>2, :has_m=>false, :has_z=>false, :name=>"latlon", :srid=>0, :type=>"GEOMETRY"}
  spatial = spatial_column_info(table_name).get(column_name, .sql_type)

  PostgreSQL::Column.new(
    column_name,
    default_value,
    ,
    !notnull,
    default_function,
    collation: collation,
    comment: comment.presence,
    serial: serial,
    spatial: spatial,
    generated: generated,
    hidden: hidden
  )
end

#primary_key(table_name) ⇒ Object

ActiveRecord allows for tables to exist without primary keys. Databases like PostgreSQL support this behavior, but CockroachDB does not. If a table is created without a primary key, CockroachDB will add a rowid column to serve as its primary key. This breaks a lot of ActiveRecord's assumptions so we'll treat tables with rowid primary keys as if they didn't have primary keys at all. www.cockroachlabs.com/docs/v19.2/create-table.html#create-a-table api.rubyonrails.org/v5.2.4/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-create_table



25
26
27
28
29
30
31
32
33
# File 'lib/active_record/connection_adapters/cockroachdb/schema_statements.rb', line 25

def primary_key(table_name)
  pk = super

  if pk == CockroachDBAdapter::DEFAULT_PRIMARY_KEY
    nil
  else
    pk
  end
end

#reset_pk_sequence!(table, pk = nil, sequence = nil) ⇒ Object

This overrides the method from PostegreSQL adapter Resets the sequence of a table's primary key to the maximum value.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/active_record/connection_adapters/cockroachdb/schema_statements.rb', line 141

def reset_pk_sequence!(table, pk = nil, sequence = nil)
  unless pk && sequence
    default_pk, default_sequence = pk_and_sequence_for(table)

    pk ||= default_pk
    sequence ||= default_sequence
  end

  if @logger && pk && !sequence
    @logger.warn "#{table} has primary key #{pk} with no default sequence."
  end

  if pk && sequence
    quoted_sequence = quote_table_name(sequence)
    max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
    if max_pk.nil?
      minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
    end

    query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA")
  end
end

#schema_creationObject



196
197
198
# File 'lib/active_record/connection_adapters/cockroachdb/schema_statements.rb', line 196

def schema_creation
  CockroachDB::SchemaCreation.new(self)
end

#spatial_column_info(table_name) ⇒ Object

memoize hash of column infos for tables



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

def spatial_column_info(table_name)
  @spatial_column_info ||= {}
  @spatial_column_info[table_name.to_sym] ||= SpatialColumnInfo.new(self, table_name.to_s)
end

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

CockroachDB will use INT8 if the SQL type is INTEGER, so we make it use INT4 explicitly when needed.

For spatial columns, include the limit to properly format the column name since type alone is not enough to format the column. Ex. type_to_sql(:geography, limit: “Point,4326”)

> “geography(Point,4326)”



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/active_record/connection_adapters/cockroachdb/schema_statements.rb', line 116

def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
  sql = \
    case type.to_s
    when "integer"
      case limit
      when nil; "int"
      when 1, 2; "int2"
      when 3, 4; "int4"
      when 5..8; "int8"
      else super
      end
    when "geometry", "geography"
      "#{type}(#{limit})"
    else
      super
    end
  # The call to super might have appeneded [] already.
  if array && type != :primary_key && !sql.end_with?("[]")
    sql = "#{sql}[]"
  end
  sql
end