torque-postgresql 3.4.1 → 4.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/lib/torque/postgresql/adapter/database_statements.rb +63 -84
  3. data/lib/torque/postgresql/adapter/oid/array.rb +17 -0
  4. data/lib/torque/postgresql/adapter/oid/line.rb +2 -6
  5. data/lib/torque/postgresql/adapter/oid/range.rb +4 -4
  6. data/lib/torque/postgresql/adapter/oid.rb +1 -23
  7. data/lib/torque/postgresql/adapter/quoting.rb +13 -7
  8. data/lib/torque/postgresql/adapter/schema_creation.rb +7 -28
  9. data/lib/torque/postgresql/adapter/schema_definitions.rb +36 -0
  10. data/lib/torque/postgresql/adapter/schema_dumper.rb +90 -34
  11. data/lib/torque/postgresql/adapter/schema_overrides.rb +45 -0
  12. data/lib/torque/postgresql/adapter/schema_statements.rb +64 -49
  13. data/lib/torque/postgresql/arel/infix_operation.rb +15 -28
  14. data/lib/torque/postgresql/arel/nodes.rb +2 -2
  15. data/lib/torque/postgresql/arel/operations.rb +7 -1
  16. data/lib/torque/postgresql/arel/visitors.rb +3 -9
  17. data/lib/torque/postgresql/associations/association_scope.rb +23 -31
  18. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +25 -0
  19. data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +16 -0
  20. data/lib/torque/postgresql/attributes/builder/enum.rb +12 -9
  21. data/lib/torque/postgresql/attributes/builder/full_text_search.rb +121 -0
  22. data/lib/torque/postgresql/attributes/builder/period.rb +21 -21
  23. data/lib/torque/postgresql/attributes/builder.rb +49 -11
  24. data/lib/torque/postgresql/attributes/enum.rb +7 -7
  25. data/lib/torque/postgresql/attributes/enum_set.rb +7 -7
  26. data/lib/torque/postgresql/attributes/full_text_search.rb +19 -0
  27. data/lib/torque/postgresql/attributes/period.rb +2 -2
  28. data/lib/torque/postgresql/attributes.rb +0 -4
  29. data/lib/torque/postgresql/auxiliary_statement/recursive.rb +3 -3
  30. data/lib/torque/postgresql/base.rb +3 -10
  31. data/lib/torque/postgresql/collector.rb +1 -1
  32. data/lib/torque/postgresql/config.rb +95 -5
  33. data/lib/torque/postgresql/function.rb +61 -0
  34. data/lib/torque/postgresql/inheritance.rb +52 -36
  35. data/lib/torque/postgresql/predicate_builder/arel_attribute_handler.rb +33 -0
  36. data/lib/torque/postgresql/predicate_builder/array_handler.rb +47 -0
  37. data/lib/torque/postgresql/predicate_builder/enumerator_lazy_handler.rb +37 -0
  38. data/lib/torque/postgresql/predicate_builder/regexp_handler.rb +21 -0
  39. data/lib/torque/postgresql/predicate_builder.rb +35 -0
  40. data/lib/torque/postgresql/railtie.rb +112 -30
  41. data/lib/torque/postgresql/reflection/abstract_reflection.rb +12 -44
  42. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +4 -0
  43. data/lib/torque/postgresql/reflection/has_many_reflection.rb +4 -0
  44. data/lib/torque/postgresql/reflection/runtime_reflection.rb +1 -1
  45. data/lib/torque/postgresql/relation/inheritance.rb +4 -7
  46. data/lib/torque/postgresql/relation.rb +6 -10
  47. data/lib/torque/postgresql/schema_cache.rb +6 -12
  48. data/lib/torque/postgresql/version.rb +1 -1
  49. data/lib/torque/postgresql.rb +2 -1
  50. data/spec/initialize.rb +58 -0
  51. data/spec/mocks/cache_query.rb +21 -21
  52. data/spec/mocks/create_table.rb +6 -26
  53. data/spec/schema.rb +19 -12
  54. data/spec/spec_helper.rb +5 -1
  55. data/spec/tests/arel_spec.rb +32 -7
  56. data/spec/tests/auxiliary_statement_spec.rb +3 -3
  57. data/spec/tests/belongs_to_many_spec.rb +72 -5
  58. data/spec/tests/enum_set_spec.rb +12 -11
  59. data/spec/tests/enum_spec.rb +4 -2
  60. data/spec/tests/full_text_seach_test.rb +252 -0
  61. data/spec/tests/function_spec.rb +42 -0
  62. data/spec/tests/has_many_spec.rb +21 -8
  63. data/spec/tests/interval_spec.rb +1 -7
  64. data/spec/tests/period_spec.rb +61 -61
  65. data/spec/tests/predicate_builder_spec.rb +132 -0
  66. data/spec/tests/schema_spec.rb +2 -8
  67. data/spec/tests/table_inheritance_spec.rb +25 -26
  68. metadata +34 -39
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c61aaf577483288d5c9a9af30704402529c86318cbdc312f567b643955c1443e
4
- data.tar.gz: ccb9930f10cd48eb08448ecd99275fd7ca7ef54443fab81476c6b3a57c03e394
3
+ metadata.gz: 4a341f61802bc246d4004168a49fc5f67156b7dc3c962321a28e73fc8288e0db
4
+ data.tar.gz: ca658f344ed87f29b94eb1e115a13b4ef6d4d954c450b6f4e9892f7788b35e39
5
5
  SHA512:
6
- metadata.gz: 34ed5cb4bb34b33de90020a7053378378b28ff0f45cc7a33e53084fa86c9069ab7694890fa0cac1ab19a85f5ee825cc317d3406cf8f98d4336ff416a2bae092b
7
- data.tar.gz: 22771a0e4b1ce1486ae0ae6700c81cbbf84b227bb78b1a7163be2d2948b7161e822bb32dfdcc399c103348e2ab89f8cdda04112f988d303fd0735e178a605043
6
+ metadata.gz: 4e83150ee16db03f72e39d85c33932f38f43351501e673133e42f7df98208cacfe34e9eb0b505ede452fae872b770c5de9c8cdded5927b5d527fc9b6a7757dc7
7
+ data.tar.gz: 91c9b79d9698c720c5ed77a775f9758069ac41c5fd625c7870180e85a8f4ed16a4998c4b93ff8de04eb6e828b689046801b9a3e7c76efeef22dc0a91b9083797
@@ -5,7 +5,7 @@ module Torque
5
5
  module Adapter
6
6
  module DatabaseStatements
7
7
 
8
- EXTENDED_DATABASE_TYPES = %i(enum enum_set interval)
8
+ EXTENDED_DATABASE_TYPES = %i[enum enum_set interval]
9
9
 
10
10
  # Switch between dump mode or not
11
11
  def dump_mode!
@@ -48,8 +48,8 @@ module Torque
48
48
  def schema_exists?(name, filtered: true)
49
49
  return user_defined_schemas.include?(name.to_s) if filtered
50
50
 
51
- query_value(<<-SQL) == 1
52
- SELECT 1 FROM pg_catalog.pg_namespace WHERE nspname = '#{name}'
51
+ query_value(<<-SQL, "SCHEMA") == 1
52
+ SELECT 1 FROM pg_catalog.pg_namespace WHERE nspname = #{quote(name)}
53
53
  SQL
54
54
  end
55
55
 
@@ -59,96 +59,75 @@ module Torque
59
59
  end
60
60
  alias data_type_exists? type_exists?
61
61
 
62
- # Configure the interval format
63
- def configure_connection
64
- super
65
- execute("SET SESSION IntervalStyle TO 'iso_8601'", 'SCHEMA')
66
- end
67
-
68
- # Since enums create new types, type map needs to be rebooted to include
69
- # the new ones, both normal and array one
70
- def create_enum(name, *)
71
- super
72
-
73
- oid = query_value("SELECT #{quote(name)}::regtype::oid", "SCHEMA").to_i
74
- load_additional_types([oid])
75
- end
76
-
77
62
  # Change some of the types being mapped
78
63
  def initialize_type_map(m = type_map)
79
64
  super
80
- m.register_type 'box', OID::Box.new
81
- m.register_type 'circle', OID::Circle.new
82
- m.register_type 'interval', OID::Interval.new
83
- m.register_type 'line', OID::Line.new
84
- m.register_type 'segment', OID::Segment.new
85
65
 
86
- m.alias_type 'regclass', 'varchar'
66
+ if PostgreSQL.config.geometry.enabled
67
+ m.register_type 'box', OID::Box.new
68
+ m.register_type 'circle', OID::Circle.new
69
+ m.register_type 'line', OID::Line.new
70
+ m.register_type 'segment', OID::Segment.new
71
+ end
72
+
73
+ if PostgreSQL.config.interval.enabled
74
+ m.register_type 'interval', OID::Interval.new
75
+ end
87
76
  end
88
77
 
89
78
  # :nodoc:
90
79
  def load_additional_types(oids = nil)
80
+ type_map.alias_type 'regclass', 'varchar'
81
+ type_map.alias_type 'regconfig', 'varchar'
91
82
  super
92
83
  torque_load_additional_types(oids)
93
84
  end
94
85
 
95
86
  # Add the composite types to be loaded too.
96
87
  def torque_load_additional_types(oids = nil)
97
- filter = ("AND a.typelem::integer IN (%s)" % oids.join(', ')) if oids
98
-
99
- query = <<-SQL
100
- SELECT a.typelem AS oid, t.typname, t.typelem,
101
- t.typdelim, t.typbasetype, t.typtype,
102
- t.typarray
103
- FROM pg_type t
104
- INNER JOIN pg_type a ON (a.oid = t.typarray)
105
- LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
106
- WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
107
- AND t.typtype IN ( 'e' )
108
- #{filter}
109
- AND NOT EXISTS(
110
- SELECT 1 FROM pg_catalog.pg_type el
111
- WHERE el.oid = t.typelem AND el.typarray = t.oid
112
- )
113
- AND (t.typrelid = 0 OR (
114
- SELECT c.relkind = 'c' FROM pg_catalog.pg_class c
115
- WHERE c.oid = t.typrelid
116
- ))
88
+ return unless torque_load_additional_types?
89
+
90
+ # Types: (b)ase, (c)omposite, (d)omain, (e)num, (p)seudotype, (r)ange
91
+ # (m)ultirange
92
+
93
+ query = <<~SQL
94
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput,
95
+ r.rngsubtype, t.typtype, t.typbasetype, t.typarray
96
+ FROM pg_type as t
97
+ LEFT JOIN pg_range as r ON oid = rngtypid
98
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
99
+ WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
117
100
  SQL
118
101
 
119
- execute_and_clear(query, 'SCHEMA', []) do |records|
120
- records.each { |row| OID::Enum.create(row, type_map) }
102
+ if oids
103
+ query += " AND t.oid IN (%s)" % oids.join(", ")
104
+ else
105
+ query += " AND t.typtype IN ('e')"
121
106
  end
107
+
108
+ options = { allow_retry: true, materialize_transactions: false }
109
+ internal_execute(query, 'SCHEMA', **options).each do |row|
110
+ if row['typtype'] == 'e' && PostgreSQL.config.enum.enabled
111
+ OID::Enum.create(row, type_map)
112
+ end
113
+ end
114
+ end
115
+
116
+ def torque_load_additional_types?
117
+ PostgreSQL.config.enum.enabled
122
118
  end
123
119
 
124
120
  # Gets a list of user defined types.
125
121
  # You can even choose the +category+ filter
126
122
  def user_defined_types(*categories)
127
- category_condition = categories.present? \
128
- ? "AND t.typtype IN ('#{categories.join("', '")}')" \
129
- : "AND t.typtype NOT IN ('b', 'd')"
130
-
131
- select_all(<<-SQL, 'SCHEMA').rows.to_h
132
- SELECT t.typname AS name,
133
- CASE t.typtype
134
- WHEN 'e' THEN 'enum'
135
- END AS type
136
- FROM pg_type t
137
- LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
123
+ categories = categories.compact.presence || %w[c e p r m]
124
+
125
+ query(<<-SQL, 'SCHEMA').to_h
126
+ SELECT t.typname, t.typtype
127
+ FROM pg_type as t
128
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
138
129
  WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
139
- #{category_condition}
140
- AND NOT EXISTS(
141
- SELECT 1
142
- FROM pg_catalog.pg_type el
143
- WHERE el.oid = t.typelem
144
- AND el.typarray = t.oid
145
- )
146
- AND (t.typrelid = 0 OR (
147
- SELECT c.relkind = 'c'
148
- FROM pg_catalog.pg_class c
149
- WHERE c.oid = t.typrelid
150
- ))
151
- ORDER BY t.typtype DESC
130
+ AND t.typtype IN ('#{categories.join("', '")}')
152
131
  SQL
153
132
  end
154
133
 
@@ -195,20 +174,20 @@ module Torque
195
174
  # Get the list of columns, and their definition, but only from the
196
175
  # actual table, does not include columns that comes from inherited table
197
176
  def column_definitions(table_name)
198
- local = 'AND a.attislocal' if @_dump_mode
199
-
200
- query(<<-SQL, 'SCHEMA')
201
- SELECT a.attname, format_type(a.atttypid, a.atttypmod),
202
- pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
203
- c.collname, col_description(a.attrelid, a.attnum) AS comment,
204
- #{supports_virtual_columns? ? 'attgenerated' : quote('')} as attgenerated
205
- FROM pg_attribute a
206
- LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
207
- LEFT JOIN pg_type t ON a.atttypid = t.oid
208
- LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
209
- WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
210
- AND a.attnum > 0 AND NOT a.attisdropped #{local}
211
- ORDER BY a.attnum
177
+ query(<<~SQL, "SCHEMA")
178
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod),
179
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
180
+ c.collname, col_description(a.attrelid, a.attnum) AS comment,
181
+ #{supports_identity_columns? ? 'attidentity' : quote('')} AS identity,
182
+ #{supports_virtual_columns? ? 'attgenerated' : quote('')} as attgenerated
183
+ FROM pg_attribute a
184
+ LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
185
+ LEFT JOIN pg_type t ON a.atttypid = t.oid
186
+ LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
187
+ WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
188
+ AND a.attnum > 0 AND NOT a.attisdropped
189
+ #{'AND a.attislocal' if @_dump_mode}
190
+ ORDER BY a.attnum
212
191
  SQL
213
192
  end
214
193
 
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Torque
4
+ module PostgreSQL
5
+ module Adapter
6
+ module OID
7
+ module Array
8
+ def force_equality?(value)
9
+ PostgreSQL.config.predicate_builder.handle_array_attributes ? false : super
10
+ end
11
+ end
12
+
13
+ ::ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.prepend(Array)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -7,9 +7,7 @@ module Torque
7
7
  alias c= intercept=
8
8
 
9
9
  def a=(value)
10
- self.slope = vertical? \
11
- ? Float::INFINITY \
12
- : Rational(value, b)
10
+ self.slope = vertical? ? Float::INFINITY : Rational(value, b)
13
11
  end
14
12
 
15
13
  def a
@@ -17,9 +15,7 @@ module Torque
17
15
  end
18
16
 
19
17
  def b=(value)
20
- self.slope = value.zero? \
21
- ? Float::INFINITY \
22
- : Rational(a, value)
18
+ self.slope = value.zero? ? Float::INFINITY : Rational(a, value)
23
19
  end
24
20
 
25
21
  def b
@@ -7,7 +7,7 @@ module Torque
7
7
  class Range < ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Range
8
8
  HASH_PICK = %i[from start end to].freeze
9
9
 
10
- module Comparasion
10
+ module Comparison
11
11
  def <=>(other)
12
12
  return super unless other.acts_like?(:date) || other.acts_like?(:time)
13
13
  other = other.to_time if other.acts_like?(:date)
@@ -17,9 +17,9 @@ module Torque
17
17
 
18
18
  def cast_value(value)
19
19
  case value
20
- when Array
20
+ when ::Array
21
21
  cast_custom(value[0], value[1])
22
- when Hash
22
+ when ::Hash
23
23
  pieces = value.with_indifferent_access.values_at(*HASH_PICK)
24
24
  cast_custom(pieces[0] || pieces[1], pieces[2] || pieces[3])
25
25
  else
@@ -54,7 +54,7 @@ module Torque
54
54
  ::ActiveRecord::ConnectionAdapters::PostgreSQL::OID.send(:remove_const, :Range)
55
55
  ::ActiveRecord::ConnectionAdapters::PostgreSQL::OID.const_set(:Range, Range)
56
56
 
57
- ::Float.prepend(Range::Comparasion)
57
+ ::Float.prepend(Range::Comparison)
58
58
  end
59
59
  end
60
60
  end
@@ -1,24 +1,2 @@
1
- require_relative 'oid/box'
2
- require_relative 'oid/circle'
3
- require_relative 'oid/enum'
4
- require_relative 'oid/enum_set'
5
- require_relative 'oid/interval'
6
- require_relative 'oid/line'
1
+ require_relative 'oid/array'
7
2
  require_relative 'oid/range'
8
- require_relative 'oid/segment'
9
-
10
- module Torque
11
- module PostgreSQL
12
- module Adapter
13
- module OID
14
- end
15
-
16
- ActiveRecord::Type.register(:box, OID::Box, adapter: :postgresql)
17
- ActiveRecord::Type.register(:circle, OID::Circle, adapter: :postgresql)
18
- ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
19
- ActiveRecord::Type.register(:enum_set, OID::EnumSet, adapter: :postgresql)
20
- ActiveRecord::Type.register(:line, OID::Line, adapter: :postgresql)
21
- ActiveRecord::Type.register(:segment, OID::Segment, adapter: :postgresql)
22
- end
23
- end
24
- end
@@ -4,21 +4,27 @@ module Torque
4
4
  module PostgreSQL
5
5
  module Adapter
6
6
  module Quoting
7
+ QUOTED_TYPE_NAMES = Concurrent::Map.new
7
8
 
8
9
  Name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name
9
10
  Column = ActiveRecord::ConnectionAdapters::PostgreSQL::Column
10
11
  ColumnDefinition = ActiveRecord::ConnectionAdapters::ColumnDefinition
12
+ Utils = ActiveRecord::ConnectionAdapters::PostgreSQL::Utils
11
13
 
12
14
  # Quotes type names for use in SQL queries.
13
- def quote_type_name(string, schema = nil)
14
- name_schema, table = string.to_s.scan(/[^".\s]+|"[^"]*"/)
15
- if table.nil?
16
- table = name_schema
17
- name_schema = nil
15
+ def quote_type_name(name, *args)
16
+ QUOTED_TYPE_NAMES[args] ||= begin
17
+ name = name.to_s
18
+ args << 'public' if args.empty? && !name.include?('.')
19
+ quote_identifier_name(name, *args)
18
20
  end
21
+ end
19
22
 
20
- schema = schema || name_schema || 'public'
21
- Name.new(schema, table).quoted
23
+ # Make sure to support all sorts of different compositions of names
24
+ def quote_identifier_name(name, schema = nil)
25
+ name = Utils.extract_schema_qualified_name(name.to_s) unless name.is_a?(Name)
26
+ name.instance_variable_set(:@schema, Utils.unquote_identifier(schema.to_s)) if schema
27
+ name.quoted.freeze
22
28
  end
23
29
 
24
30
  def quote_default_expression(value, column)
@@ -3,39 +3,18 @@ module Torque
3
3
  module Adapter
4
4
  module SchemaCreation
5
5
 
6
- # Redefine original table creation command to ensure PostgreSQL standard
7
- def visit_TableDefinition(o)
8
- create_sql = +"CREATE#{table_modifier_in_create(o)} TABLE "
9
- create_sql << "IF NOT EXISTS " if o.if_not_exists
10
- create_sql << "#{quote_table_name(o.name)} "
11
-
12
- statements = o.columns.map { |c| accept c }
13
- statements << accept(o.primary_keys) if o.primary_keys
14
-
15
- if supports_indexes_in_create?
16
- statements.concat(o.indexes.map { |c, o| index_in_create(o.name, c, o) })
17
- end
18
-
19
- if @conn.supports_foreign_keys?
20
- statements.concat(o.foreign_keys.map { |fk| accept fk })
21
- end
22
-
23
- if respond_to?(:supports_check_constraints?) && supports_check_constraints?
24
- statements.concat(o.check_constraints.map { |chk| accept chk })
25
- end
26
-
27
- create_sql << "(#{statements.join(', ')})" \
28
- if statements.present? || o.inherits.present?
29
-
30
- add_table_options!(create_sql, o)
31
-
6
+ # Inherits are now setup via table options, but keep the implementation
7
+ # supported by this gem
8
+ def add_table_options!(create_sql, o)
32
9
  if o.inherits.present?
10
+ # Make sure we always have parenthesis
11
+ create_sql << '()' unless create_sql[-1] == ')'
12
+
33
13
  tables = o.inherits.map(&method(:quote_table_name))
34
14
  create_sql << " INHERITS ( #{tables.join(' , ')} )"
35
15
  end
36
16
 
37
- create_sql << " AS #{to_sql(o.as)}" if o.as
38
- create_sql
17
+ super(create_sql, o)
39
18
  end
40
19
  end
41
20
 
@@ -3,7 +3,26 @@
3
3
  module Torque
4
4
  module PostgreSQL
5
5
  module Adapter
6
+ module ColumnMethods
7
+
8
+ # Adds a search language column to the table. See +add_search_language+
9
+ def search_language(*names, **options)
10
+ raise ArgumentError, "Missing column name(s) for search_language" if names.empty?
11
+ names.each { |name| column(name, :regconfig, **options) }
12
+ end
13
+
14
+ # Add a search vector column to the table. See +add_search_vector+
15
+ def search_vector(*names, columns:, **options)
16
+ raise ArgumentError, "Missing column name(s) for search_vector" if names.empty?
17
+ options = Attributes::Builder.search_vector_options(columns: columns, **options)
18
+ names.each { |name| column(name, :virtual, **options) }
19
+ end
20
+
21
+ end
22
+
6
23
  module TableDefinition
24
+ include ColumnMethods
25
+
7
26
  attr_reader :inherits
8
27
 
9
28
  def initialize(*args, **options)
@@ -12,8 +31,25 @@ module Torque
12
31
  @inherits = Array.wrap(options.delete(:inherits)).flatten.compact \
13
32
  if options.key?(:inherits)
14
33
  end
34
+
35
+ def set_primary_key(tn, id, primary_key, *, **)
36
+ super unless @inherits.present? && primary_key.blank? && id == :primary_key
37
+ end
38
+
39
+ private
40
+
41
+ def create_column_definition(name, type, options)
42
+ if type == :enum_set
43
+ type = :enum
44
+ options ||= {}
45
+ options[:array] = true
46
+ end
47
+
48
+ super(name, type, options)
49
+ end
15
50
  end
16
51
 
52
+ ActiveRecord::ConnectionAdapters::PostgreSQL::Table.include ColumnMethods
17
53
  ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition.include TableDefinition
18
54
  end
19
55
  end
@@ -4,6 +4,14 @@ module Torque
4
4
  module PostgreSQL
5
5
  module Adapter
6
6
  module SchemaDumper
7
+ SEARCH_VECTOR_SCANNER = /
8
+ to_tsvector\(
9
+ ('[^']+'|[a-z][a-z0-9_]*)[^,]*,[^\(]*
10
+ \(?coalesce\(([a-z][a-z0-9_]*)[^\)]*\)\)?
11
+ (?:::[^\)]*\))?
12
+ (?:\s*,\s*'([A-D])')?
13
+ /ix
14
+
7
15
  def dump(stream) # :nodoc:
8
16
  @connection.dump_mode!
9
17
  super
@@ -12,16 +20,6 @@ module Torque
12
20
  stream
13
21
  end
14
22
 
15
- def extensions(stream) # :nodoc:
16
- super
17
- user_defined_schemas(stream)
18
- end
19
-
20
- # Translate +:enum_set+ into +:enum+
21
- def schema_type(column)
22
- column.type == :enum_set ? :enum : super
23
- end
24
-
25
23
  private
26
24
 
27
25
  def tables(stream) # :nodoc:
@@ -39,41 +37,46 @@ module Torque
39
37
 
40
38
  def dump_tables(stream)
41
39
  inherited_tables = @connection.inherited_tables
42
- sorted_tables = (@connection.tables - @connection.views).sort_by do |table_name|
43
- table_name.split(/(?:public)?\./).reverse
44
- end
40
+ sorted_tables = (@connection.tables - @connection.views).filter_map do |table_name|
41
+ name_parts = table_name.split(/(?:public)?\./).reverse.compact_blank
42
+ next if ignored?(table_name) || ignored?(name_parts.join('.'))
43
+
44
+ [table_name, name_parts]
45
+ end.sort_by(&:last).to_h
46
+
47
+ postponed = []
45
48
 
46
49
  stream.puts " # These are the common tables"
47
- (sorted_tables - inherited_tables.keys).each do |table_name|
48
- table(table_name, stream) unless ignored?(table_name)
50
+ sorted_tables.each do |table, (table_name, _)|
51
+ next postponed << table if inherited_tables.key?(table_name)
52
+
53
+ table(table, stream)
54
+ stream.puts # Ideally we would not do this in the last one
49
55
  end
50
56
 
51
- if inherited_tables.present?
57
+ if postponed.present?
52
58
  stream.puts " # These are tables that have inheritance"
53
- inherited_tables.each do |table_name, inherits|
54
- next if ignored?(table_name)
55
-
59
+ postponed.each do |table|
56
60
  sub_stream = StringIO.new
57
- table(table_name, sub_stream)
58
-
59
- # Add the inherits setting
60
- sub_stream.rewind
61
- inherits.map! { |parent| parent.to_s.sub(/\Apublic\./, '') }
62
- inherits = inherits.first if inherits.size === 1
63
- inherits = ", inherits: #{inherits.inspect} do |t|"
64
- table_dump = sub_stream.read.gsub(/ do \|t\|$/, inherits)
65
-
66
- # Ensure bodyless definitions
67
- table_dump.gsub!(/do \|t\|\n end/, '')
68
- stream.print table_dump
61
+ table(table, sub_stream)
62
+ stream.puts sub_stream.string.sub(/do \|t\|\n end/, '')
63
+ stream.puts
69
64
  end
70
65
  end
71
66
 
72
- # Dump foreign keys at the end to make sure all dependent tables exist.
67
+ # Fixes double new lines to single new lines
68
+ stream.pos -= 1
69
+
70
+ # dump foreign keys at the end to make sure all dependent tables exist.
73
71
  if @connection.supports_foreign_keys?
72
+ foreign_keys_stream = StringIO.new
74
73
  sorted_tables.each do |tbl|
75
- foreign_keys(tbl, stream) unless ignored?(tbl)
74
+ foreign_keys(tbl, foreign_keys_stream)
76
75
  end
76
+
77
+ foreign_keys_string = foreign_keys_stream.string
78
+ stream.puts if foreign_keys_string.length > 0
79
+ stream.print foreign_keys_string
77
80
  end
78
81
  end
79
82
 
@@ -83,7 +86,8 @@ module Torque
83
86
  end
84
87
 
85
88
  # Dump user defined schemas
86
- def user_defined_schemas(stream)
89
+ def schemas(stream)
90
+ return super if !PostgreSQL.config.schemas.enabled
87
91
  return if (list = (@connection.user_defined_schemas - ['public'])).empty?
88
92
 
89
93
  stream.puts " # Custom schemas defined in this database."
@@ -91,6 +95,58 @@ module Torque
91
95
  stream.puts
92
96
  end
93
97
 
98
+ # Adjust the schema type for search vector
99
+ def schema_type_with_virtual(column)
100
+ column.virtual? && column.type == :tsvector ? :search_vector : super
101
+ end
102
+
103
+ # Adjust the schema type for search language
104
+ def schema_type(column)
105
+ column.sql_type == 'regconfig' ? :search_language : super
106
+ end
107
+
108
+ # Adjust table options to make the dump more readable
109
+ def prepare_column_options(column)
110
+ options = super
111
+ parse_search_vector_options(column, options) if column.type == :tsvector
112
+ options
113
+ end
114
+
115
+ # Parse the search vector operation into a readable format
116
+ def parse_search_vector_options(column, options)
117
+ settings = options[:as].scan(SEARCH_VECTOR_SCANNER)
118
+ return if settings.empty?
119
+
120
+ languages = settings.map(&:shift).uniq
121
+ return if languages.many?
122
+
123
+ language = languages.first
124
+ language = language[0] == "'" ? language[1..-2] : language.to_sym
125
+ columns = parse_search_vector_columns(settings)
126
+
127
+ options.except!(:as, :type)
128
+ options.merge!(language: language.inspect, columns: columns)
129
+ end
130
+
131
+ # Simplify the whole columns configuration to make it more manageable
132
+ def parse_search_vector_columns(settings)
133
+ return ":#{settings.first.first}" if settings.one?
134
+
135
+ settings = settings.sort_by(&:last)
136
+ weights = %w[A B C D]
137
+
138
+ columns = settings.each.with_index.reduce([]) do |acc, (setting, index)|
139
+ column, weight = setting
140
+ break if (weights[index] || 'D') != weight
141
+
142
+ acc << column
143
+ acc
144
+ end
145
+
146
+ return columns.map(&:to_sym).inspect if columns
147
+ settings.to_h.transform_values(&:inspect)
148
+ end
149
+
94
150
  def fx_functions_position
95
151
  return unless defined?(::Fx::SchemaDumper::Function)
96
152
  Fx.configuration.dump_functions_at_beginning_of_schema ? :beginning : :end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Torque
4
+ module PostgreSQL
5
+ module Adapter
6
+ module SchemaOverrides
7
+ # This adds better support for handling the quotation of table names
8
+ def quote_table_name(name)
9
+ ActiveRecord::ConnectionAdapters::PostgreSQL::Quoting::QUOTED_TABLE_NAMES.then do |m|
10
+ m[name] ||= quote_identifier_name(name)
11
+ end
12
+ end
13
+
14
+ %i[
15
+ table_exists? indexes index_exists? columns column_exists? primary_key
16
+ create_table change_table add_column add_columns remove_columns remove_column
17
+ change_column change_column_default change_column_null rename_column
18
+ add_index remove_index rename_index index_name_exists? foreign_keys
19
+ add_timestamps remove_timestamps change_table_comment change_column_comment
20
+ bulk_change_table
21
+
22
+ rename_table add_foreign_key remove_foreign_key foreign_key_exists?
23
+ ].each do |method_name|
24
+ define_method(method_name) do |table_name, *args, **options, &block|
25
+ table_name = sanitize_name_with_schema(table_name, options)
26
+ super(table_name, *args, **options, &block)
27
+ end
28
+ end
29
+
30
+ def drop_table(*table_names, **options)
31
+ table_names = table_names.map { |name| sanitize_name_with_schema(name, options.dup) }
32
+ super(*table_names, **options)
33
+ end
34
+
35
+ private
36
+
37
+ def validate_table_length!(table_name)
38
+ super(table_name.to_s)
39
+ end
40
+ end
41
+
42
+ include SchemaOverrides
43
+ end
44
+ end
45
+ end