torque-postgresql 4.0.1 → 4.1.1

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 (218) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +52 -0
  3. data/lib/generators/torque/skill_generator.rb +17 -0
  4. data/lib/generators/torque/templates/trigger.sql.erb +3 -0
  5. data/lib/generators/torque/trigger_generator.rb +13 -0
  6. data/lib/torque/postgresql/adapter/database_statements.rb +98 -7
  7. data/lib/torque/postgresql/adapter/inheritance_statements.rb +297 -0
  8. data/lib/torque/postgresql/adapter/oid/array.rb +13 -0
  9. data/lib/torque/postgresql/adapter/oid/box.rb +1 -1
  10. data/lib/torque/postgresql/adapter/oid/circle.rb +1 -1
  11. data/lib/torque/postgresql/adapter/oid/composite.rb +117 -0
  12. data/lib/torque/postgresql/adapter/oid/enum.rb +1 -1
  13. data/lib/torque/postgresql/adapter/oid/enum_set.rb +2 -2
  14. data/lib/torque/postgresql/adapter/oid/interval.rb +1 -1
  15. data/lib/torque/postgresql/adapter/oid/line.rb +1 -1
  16. data/lib/torque/postgresql/adapter/oid/lquery.rb +70 -0
  17. data/lib/torque/postgresql/adapter/oid/ltree.rb +111 -0
  18. data/lib/torque/postgresql/adapter/oid/segment.rb +1 -1
  19. data/lib/torque/postgresql/adapter/oid/struct.rb +150 -0
  20. data/lib/torque/postgresql/adapter/oid/struct_list.rb +52 -0
  21. data/lib/torque/postgresql/adapter/oid/struct_set.rb +38 -0
  22. data/lib/torque/postgresql/adapter/oid.rb +13 -0
  23. data/lib/torque/postgresql/adapter/quoting.rb +11 -2
  24. data/lib/torque/postgresql/adapter/schema_definitions.rb +51 -0
  25. data/lib/torque/postgresql/adapter/schema_dumper.rb +27 -0
  26. data/lib/torque/postgresql/adapter/schema_statements.rb +207 -0
  27. data/lib/torque/postgresql/adapter.rb +2 -5
  28. data/lib/torque/postgresql/arel/nodes.rb +114 -1
  29. data/lib/torque/postgresql/arel/visitors.rb +65 -8
  30. data/lib/torque/postgresql/associations/join_dependency.rb +55 -0
  31. data/lib/torque/postgresql/associations.rb +3 -0
  32. data/lib/torque/postgresql/attributes/base.rb +97 -0
  33. data/lib/torque/postgresql/attributes/composite.rb +98 -0
  34. data/lib/torque/postgresql/attributes/enum.rb +13 -7
  35. data/lib/torque/postgresql/attributes/lquery.rb +120 -0
  36. data/lib/torque/postgresql/attributes/ltree.rb +121 -0
  37. data/lib/torque/postgresql/attributes/simple_enum.rb +72 -0
  38. data/lib/torque/postgresql/attributes/struct.rb +137 -0
  39. data/lib/torque/postgresql/base.rb +26 -15
  40. data/lib/torque/postgresql/config.rb +100 -15
  41. data/lib/torque/postgresql/geometry_builder.rb +4 -10
  42. data/lib/torque/postgresql/inheritance/expander.rb +66 -0
  43. data/lib/torque/postgresql/inheritance/record.rb +52 -0
  44. data/lib/torque/postgresql/inheritance.rb +141 -68
  45. data/lib/torque/postgresql/migration/command_recorder.rb +76 -0
  46. data/lib/torque/postgresql/predicate_builder/arel_attribute_handler.rb +1 -1
  47. data/lib/torque/postgresql/predicate_builder/array_handler.rb +16 -8
  48. data/lib/torque/postgresql/predicate_builder/composite_handler.rb +122 -0
  49. data/lib/torque/postgresql/predicate_builder/ltree_handler.rb +112 -0
  50. data/lib/torque/postgresql/predicate_builder/struct_handler.rb +88 -0
  51. data/lib/torque/postgresql/predicate_builder.rb +53 -0
  52. data/lib/torque/postgresql/predicate_table.rb +61 -0
  53. data/lib/torque/postgresql/railtie.rb +52 -0
  54. data/lib/torque/postgresql/relation/auxiliary_statement.rb +7 -3
  55. data/lib/torque/postgresql/relation/buckets.rb +2 -1
  56. data/lib/torque/postgresql/relation/cross_join.rb +193 -0
  57. data/lib/torque/postgresql/relation/inheritance.rb +145 -28
  58. data/lib/torque/postgresql/relation/join_series.rb +1 -0
  59. data/lib/torque/postgresql/relation/merger.rb +30 -5
  60. data/lib/torque/postgresql/relation/segments.rb +140 -0
  61. data/lib/torque/postgresql/relation.rb +32 -12
  62. data/lib/torque/postgresql/schema_cache.rb +1 -0
  63. data/lib/torque/postgresql/validations.rb +29 -0
  64. data/lib/torque/postgresql/version.rb +1 -1
  65. data/lib/torque/postgresql/versioned_commands/command_migration.rb +24 -1
  66. data/lib/torque/postgresql/versioned_commands.rb +39 -0
  67. data/lib/torque/postgresql.rb +4 -0
  68. data/sig/generators/torque/function_generator.rbs +9 -0
  69. data/sig/generators/torque/skill_generator.rbs +7 -0
  70. data/sig/generators/torque/trigger_generator.rbs +9 -0
  71. data/sig/generators/torque/type_generator.rbs +9 -0
  72. data/sig/generators/torque/view_generator.rbs +9 -0
  73. data/sig/manifest.yaml +7 -0
  74. data/sig/torque/postgresql/adapter/database_statements.rbs +42 -0
  75. data/sig/torque/postgresql/adapter/inheritance_statements.rbs +36 -0
  76. data/sig/torque/postgresql/adapter/oid/array.rbs +20 -0
  77. data/sig/torque/postgresql/adapter/oid/box.rbs +28 -0
  78. data/sig/torque/postgresql/adapter/oid/circle.rbs +33 -0
  79. data/sig/torque/postgresql/adapter/oid/composite.rbs +35 -0
  80. data/sig/torque/postgresql/adapter/oid/enum.rbs +28 -0
  81. data/sig/torque/postgresql/adapter/oid/enum_set.rbs +19 -0
  82. data/sig/torque/postgresql/adapter/oid/interval.rbs +20 -0
  83. data/sig/torque/postgresql/adapter/oid/line.rbs +33 -0
  84. data/sig/torque/postgresql/adapter/oid/lquery.rbs +21 -0
  85. data/sig/torque/postgresql/adapter/oid/ltree.rbs +32 -0
  86. data/sig/torque/postgresql/adapter/oid/range.rbs +28 -0
  87. data/sig/torque/postgresql/adapter/oid/segment.rbs +38 -0
  88. data/sig/torque/postgresql/adapter/oid/struct.rbs +41 -0
  89. data/sig/torque/postgresql/adapter/oid/struct_list.rbs +20 -0
  90. data/sig/torque/postgresql/adapter/oid/struct_set.rbs +18 -0
  91. data/sig/torque/postgresql/adapter/oid.rbs +9 -0
  92. data/sig/torque/postgresql/adapter/quoting.rbs +20 -0
  93. data/sig/torque/postgresql/adapter/schema_creation.rbs +13 -0
  94. data/sig/torque/postgresql/adapter/schema_definitions.rbs +55 -0
  95. data/sig/torque/postgresql/adapter/schema_dumper.rbs +37 -0
  96. data/sig/torque/postgresql/adapter/schema_overrides.rbs +46 -0
  97. data/sig/torque/postgresql/adapter/schema_statements.rbs +50 -0
  98. data/sig/torque/postgresql/adapter.rbs +24 -0
  99. data/sig/torque/postgresql/arel/infix_operation.rbs +79 -0
  100. data/sig/torque/postgresql/arel/join_source.rbs +19 -0
  101. data/sig/torque/postgresql/arel/nodes.rbs +83 -0
  102. data/sig/torque/postgresql/arel/operations.rbs +28 -0
  103. data/sig/torque/postgresql/arel/select_manager.rbs +15 -0
  104. data/sig/torque/postgresql/arel/visitors.rbs +30 -0
  105. data/sig/torque/postgresql/arel.rbs +6 -0
  106. data/sig/torque/postgresql/associations/association_scope.rbs +28 -0
  107. data/sig/torque/postgresql/associations/belongs_to_many_association.rbs +51 -0
  108. data/sig/torque/postgresql/associations/builder/belongs_to_many.rbs +24 -0
  109. data/sig/torque/postgresql/associations/builder/has_many.rbs +15 -0
  110. data/sig/torque/postgresql/associations/builder.rbs +8 -0
  111. data/sig/torque/postgresql/associations/foreign_association.rbs +16 -0
  112. data/sig/torque/postgresql/associations/join_dependency.rbs +16 -0
  113. data/sig/torque/postgresql/associations/preloader/association.rbs +32 -0
  114. data/sig/torque/postgresql/associations/preloader/loader_query.rbs +20 -0
  115. data/sig/torque/postgresql/associations/preloader.rbs +8 -0
  116. data/sig/torque/postgresql/associations.rbs +17 -0
  117. data/sig/torque/postgresql/attributes/base.rbs +37 -0
  118. data/sig/torque/postgresql/attributes/builder/enum.rbs +38 -0
  119. data/sig/torque/postgresql/attributes/builder/full_text_search.rbs +27 -0
  120. data/sig/torque/postgresql/attributes/builder/period.rbs +106 -0
  121. data/sig/torque/postgresql/attributes/builder.rbs +19 -0
  122. data/sig/torque/postgresql/attributes/composite.rbs +28 -0
  123. data/sig/torque/postgresql/attributes/enum.rbs +69 -0
  124. data/sig/torque/postgresql/attributes/enum_set.rbs +75 -0
  125. data/sig/torque/postgresql/attributes/full_text_search.rbs +13 -0
  126. data/sig/torque/postgresql/attributes/lazy.rbs +17 -0
  127. data/sig/torque/postgresql/attributes/lquery.rbs +48 -0
  128. data/sig/torque/postgresql/attributes/ltree.rbs +46 -0
  129. data/sig/torque/postgresql/attributes/period.rbs +13 -0
  130. data/sig/torque/postgresql/attributes/simple_enum.rbs +39 -0
  131. data/sig/torque/postgresql/attributes/struct.rbs +38 -0
  132. data/sig/torque/postgresql/attributes.rbs +6 -0
  133. data/sig/torque/postgresql/autosave_association.rbs +16 -0
  134. data/sig/torque/postgresql/auxiliary_statement/recursive.rbs +22 -0
  135. data/sig/torque/postgresql/auxiliary_statement/settings.rbs +63 -0
  136. data/sig/torque/postgresql/auxiliary_statement.rbs +57 -0
  137. data/sig/torque/postgresql/base.rbs +41 -0
  138. data/sig/torque/postgresql/collector.rbs +7 -0
  139. data/sig/torque/postgresql/config.rbs +9 -0
  140. data/sig/torque/postgresql/function.rbs +21 -0
  141. data/sig/torque/postgresql/geometry_builder.rbs +20 -0
  142. data/sig/torque/postgresql/i18n.rbs +15 -0
  143. data/sig/torque/postgresql/inheritance/expander.rbs +23 -0
  144. data/sig/torque/postgresql/inheritance/record.rbs +20 -0
  145. data/sig/torque/postgresql/inheritance.rbs +47 -0
  146. data/sig/torque/postgresql/insert_all.rbs +22 -0
  147. data/sig/torque/postgresql/migration/command_recorder.rbs +30 -0
  148. data/sig/torque/postgresql/migration.rbs +6 -0
  149. data/sig/torque/postgresql/predicate_builder/arel_attribute_handler.rbs +15 -0
  150. data/sig/torque/postgresql/predicate_builder/array_handler.rbs +26 -0
  151. data/sig/torque/postgresql/predicate_builder/composite_handler.rbs +26 -0
  152. data/sig/torque/postgresql/predicate_builder/enumerator_lazy_handler.rbs +18 -0
  153. data/sig/torque/postgresql/predicate_builder/ltree_handler.rbs +36 -0
  154. data/sig/torque/postgresql/predicate_builder/regexp_handler.rbs +16 -0
  155. data/sig/torque/postgresql/predicate_builder/struct_handler.rbs +24 -0
  156. data/sig/torque/postgresql/predicate_builder.rbs +23 -0
  157. data/sig/torque/postgresql/predicate_table.rbs +24 -0
  158. data/sig/torque/postgresql/railtie.rbs +10 -0
  159. data/sig/torque/postgresql/reflection/abstract_reflection.rbs +22 -0
  160. data/sig/torque/postgresql/reflection/association_reflection.rbs +18 -0
  161. data/sig/torque/postgresql/reflection/belongs_to_many_reflection.rbs +30 -0
  162. data/sig/torque/postgresql/reflection/has_many_reflection.rbs +14 -0
  163. data/sig/torque/postgresql/reflection/runtime_reflection.rbs +18 -0
  164. data/sig/torque/postgresql/reflection/through_reflection.rbs +14 -0
  165. data/sig/torque/postgresql/reflection.rbs +11 -0
  166. data/sig/torque/postgresql/relation/auxiliary_statement.rbs +29 -0
  167. data/sig/torque/postgresql/relation/buckets.rbs +35 -0
  168. data/sig/torque/postgresql/relation/cross_join.rbs +63 -0
  169. data/sig/torque/postgresql/relation/distinct_on.rbs +16 -0
  170. data/sig/torque/postgresql/relation/inheritance.rbs +48 -0
  171. data/sig/torque/postgresql/relation/join_series.rbs +28 -0
  172. data/sig/torque/postgresql/relation/merger.rbs +23 -0
  173. data/sig/torque/postgresql/relation/segments.rbs +27 -0
  174. data/sig/torque/postgresql/relation.rbs +48 -0
  175. data/sig/torque/postgresql/schema_cache/bound_schema_reflection.rbs +14 -0
  176. data/sig/torque/postgresql/schema_cache/inheritance.rbs +12 -0
  177. data/sig/torque/postgresql/schema_cache/schema_reflection.rbs +14 -0
  178. data/sig/torque/postgresql/schema_cache.rbs +43 -0
  179. data/sig/torque/postgresql/table_name.rbs +20 -0
  180. data/sig/torque/postgresql/validations.rbs +13 -0
  181. data/sig/torque/postgresql/version.rbs +5 -0
  182. data/sig/torque/postgresql/versioned_commands/command_migration.rbs +54 -0
  183. data/sig/torque/postgresql/versioned_commands/generator.rbs +17 -0
  184. data/sig/torque/postgresql/versioned_commands/migration_context.rbs +30 -0
  185. data/sig/torque/postgresql/versioned_commands/migrator.rbs +19 -0
  186. data/sig/torque/postgresql/versioned_commands/schema_table.rbs +23 -0
  187. data/sig/torque/postgresql/versioned_commands.rbs +25 -0
  188. data/sig/torque/postgresql.rbs +26 -0
  189. data/skills/torque-postgresql/SKILL.md +70 -0
  190. data/spec/fixtures/migrations/20250101000008_create_trigger_audit_users_v1.sql +3 -0
  191. data/spec/initialize.rb +29 -2
  192. data/spec/mocks/cache_query.rb +13 -1
  193. data/spec/models/author.rb +1 -1
  194. data/spec/models/comment.rb +2 -0
  195. data/spec/models/place.rb +2 -0
  196. data/spec/models/profile.rb +31 -0
  197. data/spec/rbs_helper.rb +8 -0
  198. data/spec/schema.rb +35 -4
  199. data/spec/spec_helper.rb +1 -0
  200. data/spec/tests/arel_spec.rb +3 -3
  201. data/spec/tests/auxiliary_statement_spec.rb +12 -0
  202. data/spec/tests/buckets_spec.rb +122 -0
  203. data/spec/tests/composite_spec.rb +867 -0
  204. data/spec/tests/cross_join_spec.rb +185 -0
  205. data/spec/tests/distinct_on_spec.rb +5 -0
  206. data/spec/tests/enum_set_spec.rb +4 -0
  207. data/spec/tests/enum_spec.rb +20 -4
  208. data/spec/tests/geometric_builder_spec.rb +16 -5
  209. data/spec/tests/has_many_spec.rb +2 -1
  210. data/spec/tests/interval_spec.rb +6 -0
  211. data/spec/tests/ltree_spec.rb +886 -0
  212. data/spec/tests/predicate_builder_spec.rb +120 -0
  213. data/spec/tests/relation_spec.rb +25 -69
  214. data/spec/tests/segments_spec.rb +264 -0
  215. data/spec/tests/struct_spec.rb +1170 -0
  216. data/spec/tests/table_inheritance_spec.rb +1037 -56
  217. data/spec/tests/versioned_commands_spec.rb +145 -0
  218. metadata +198 -2
@@ -0,0 +1,54 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module VersionedCommands
4
+ module Migration
5
+ @command: CommandMigration
6
+ @description: [String, String]
7
+ @connection: connection?
8
+ @execution_strategy: untyped
9
+
10
+ def initialize: (*untyped args) -> void
11
+ def migrate: (:up | :down direction) -> untyped
12
+ def exec_migration: (connection conn, :up | :down direction) -> untyped
13
+ def announce: (String message) -> untyped
14
+ def description_for: (:up | :down direction) -> [String, String]
15
+ def execute: (String command) -> untyped
16
+ end
17
+
18
+ class CommandMigration < ::Struct[untyped]
19
+ @migration: ActiveRecord::Migration?
20
+
21
+ def filename: () -> String
22
+ def filename=: (String value) -> String
23
+ def version: () -> Integer
24
+ def version=: (Integer value) -> Integer
25
+ def op: () -> String
26
+ def op=: (String value) -> String
27
+ def type: () -> String
28
+ def type=: (String value) -> String
29
+ def object_name: () -> String
30
+ def object_name=: (String value) -> String
31
+ def op_version: () -> Integer
32
+ def op_version=: (Integer value) -> Integer
33
+ def scope: () -> String?
34
+ def scope=: (String? value) -> String?
35
+
36
+ def execute: (String command) -> untyped
37
+ def initialize: (String filename, *untyped args) -> void
38
+ def name: () -> String
39
+ def disable_ddl_transaction: () -> false
40
+ def migrate: (:up | :down direction) -> untyped
41
+ def up: () -> untyped
42
+ def down: () -> untyped
43
+ def drop: () -> untyped
44
+
45
+ private
46
+
47
+ def drop_function: () -> untyped
48
+ def drop_type: () -> untyped
49
+ def drop_trigger: () -> untyped
50
+ def drop_view: () -> untyped
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,17 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module VersionedCommands
4
+ module Generator
5
+ TEMPLATES_PATH: String
6
+
7
+ attr_reader file_name: String?
8
+
9
+ def self.included: (singleton(Rails::Generators::Base) base) -> untyped
10
+ def type: () -> String
11
+ def create_migration_file: () -> untyped
12
+ def count_object_entries: () -> Integer
13
+ def validate_file_name!: () -> nil
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ class IllegalCommandTypeError < ActiveRecord::MigrationError
4
+ def initialize: (String file) -> void
5
+ end
6
+
7
+ module VersionedCommands
8
+ module MigrationContext
9
+ type status = [String, String, String]
10
+
11
+ InvalidMigrationTimestampError: untyped
12
+ PGAdapter: singleton(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
13
+
14
+ def migrations: () -> Array[untyped]
15
+ def migrations_status: () -> Array[status]
16
+ def migration_commands: () -> Array[CommandMigration]
17
+
18
+ private
19
+
20
+ def running_for_pg?: () -> bool
21
+ def command_files: () -> Array[String]
22
+ def parse_command_filename: (String filename) -> Array[String?]?
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ class ActiveRecord::MigrationContext
29
+ prepend Torque::PostgreSQL::VersionedCommands::MigrationContext
30
+ end
@@ -0,0 +1,19 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module VersionedCommands
4
+ module Migrator
5
+ @versioned_command: CommandMigration | false
6
+ @versioned_table: SchemaTable
7
+ @versioned_counter: Integer
8
+
9
+ def execute_migration_in_transaction: (untyped migration) -> untyped
10
+ def record_version_state_after_migrating: (Integer version) -> untyped
11
+ def versioned_command?: (untyped migration) -> bool
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ class ActiveRecord::Migrator
18
+ prepend Torque::PostgreSQL::VersionedCommands::Migrator
19
+ end
@@ -0,0 +1,23 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module VersionedCommands
4
+ class SchemaTable
5
+ attr_reader arel_table: arel_table
6
+
7
+ @pool: pool
8
+
9
+ def initialize: (pool pool) -> void
10
+ def create_version: (CommandMigration command) -> untyped
11
+ def delete_version: (CommandMigration command) -> Integer
12
+ def primary_key: () -> String
13
+ def name: () -> String
14
+ def table_name: () -> String
15
+ def create_table: () -> untyped
16
+ def drop_table: () -> untyped
17
+ def count: () -> Integer
18
+ def table_exists?: () -> bool
19
+ def versions_of: (Symbol | String type) -> Array[[String, Integer]]
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module VersionedCommands
4
+ type command_type = :function | :type | :view | :trigger
5
+ type dirs = String | Array[String]
6
+
7
+ RAILS_APP: untyped
8
+ NAME_MATCH: String
9
+
10
+ @filename_regexp: Regexp
11
+
12
+ def self.valid_type?: (Symbol | String type) -> bool
13
+ def self.validate!: (Symbol | String type, String content, String name) -> nil
14
+ def self.fetch_command: (dirs dirs, Symbol | String type, String name, Integer | String version) -> String
15
+ def self.filename_regexp: () -> Regexp
16
+
17
+ private
18
+
19
+ def self.validate_function!: (String content, String name) -> nil
20
+ def self.validate_type!: (String content, String name) -> nil
21
+ def self.validate_trigger!: (String content, String name) -> nil
22
+ def self.validate_view!: (String content, String name) -> nil
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ type model = singleton(ActiveRecord::Base)
4
+ type record = ActiveRecord::Base
5
+ type relation = ActiveRecord::Relation
6
+ type connection = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
7
+ type pool = ActiveRecord::ConnectionAdapters::ConnectionPool
8
+ type reflection = ActiveRecord::Reflection::AssociationReflection
9
+ type any_reflection = ActiveRecord::Reflection::AbstractReflection
10
+ type column = ActiveRecord::ConnectionAdapters::PostgreSQL::Column
11
+ type type_caster = ActiveModel::Type::Value
12
+ type column_ref = String | Symbol
13
+ type table_ref = String | Symbol | ActiveRecord::ConnectionAdapters::PostgreSQL::Name
14
+ type arel_table = ::Arel::Table
15
+ type arel_attribute = ::Arel::Attributes::Attribute
16
+ type arel_node = ::Arel::Nodes::Node | ::Arel::Nodes::SqlLiteral | arel_attribute
17
+ type collector = ::Arel::Collectors::SQLString | ::Arel::Collectors::Composite | ::Arel::Collectors::Bind | ::Arel::Collectors::SubstituteBinds
18
+ type stream = IO | StringIO
19
+ type table_node = arel_table | ::Arel::Nodes::TableAlias
20
+ type path_key = column_ref | Integer
21
+
22
+ interface _Callable
23
+ def call: (*untyped) -> untyped
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: torque-postgresql
3
+ description: PostgreSQL features in Rails through the torque-postgresql gem. Use when writing models, migrations, or queries in an app whose Gemfile has torque-postgresql, or when a task needs a PostgreSQL type (enum, enum set, jsonb document, composite, ltree, range, interval, geometry) or query feature (CTE, DISTINCT ON, FILTER aggregates, width_bucket, generate_series, full-text search, array associations, table inheritance).
4
+ ---
5
+
6
+ # torque-postgresql
7
+
8
+ The gem exposes PostgreSQL features through the regular Rails DSL: migration methods that dump
9
+ to `schema.rb`, model macros, and relation methods. Reach for it before raw SQL.
10
+
11
+ ## Steps
12
+
13
+ 1. Locate the installed gem with `bundle info torque-postgresql`. Done when you know its version
14
+ and path.
15
+ 2. Read the app's `Torque::PostgreSQL.configure` block (search `config/` for it). Done when you
16
+ know which features are off and which macros are renamed: every `base_method` key renames a
17
+ macro, and `versioned_commands` and `cross_join` stay off unless enabled there.
18
+ 3. Pick the feature from the table below and read its docs page before writing code. Done when
19
+ the migration, the model macro, and the query you plan all appear on that page.
20
+ 4. Check exact signatures in `<gem path>/sig/`, when the installed release ships one. Methods
21
+ generated per attribute (`admin?`, period scopes, `full_text_search`) are named on the docs
22
+ page, never in `sig/`.
23
+ 5. Write the migration with the gem's DSL, run it, and confirm `db/schema.rb` dumps it.
24
+
25
+ ## Features
26
+
27
+ Docs live at `https://torque.dev/postgresql/<page>/`. The index is
28
+ `https://torque.dev/postgresql/llms.txt`, and every page in one file is
29
+ `https://torque.dev/postgresql/llms-full.txt`.
30
+
31
+ | Need | Migration | Model or query | Page |
32
+ |---|---|---|---|
33
+ | Fixed list of values | `create_enum`, `t.enum enum_type:` | `torque_enum :role`, `Enum::Roles` | `data-types/enum` |
34
+ | Several values of one enum | `t.enum enum_type:, array: true` | `torque_enum_set :perms` | `data-types/enum-set` |
35
+ | Typed jsonb document | `t.jsonb` | `struct_for :settings, Klass` | `data-types/struct` |
36
+ | Database composite type | `create_composite_type`, `t.composite composite_type:` | `Composite::Address` | `data-types/composite` |
37
+ | Tree paths | `enable_extension 'ltree'`, `t.ltree`, `t.lquery` | `where(path: ...)` | `data-types/ltree` |
38
+ | Time windows | `t.tsrange`, `t.daterange` | `period_for :period` | `data-types/date-time-range` |
39
+ | Durations | `t.interval` | `ActiveSupport::Duration` values | `data-types/interval` |
40
+ | Geometry | `t.box`, `t.circle`, `t.line`, `t.segment` | value objects | `data-types/box` and siblings |
41
+ | Array of foreign keys | `t.bigint :tag_ids, array: true` | `belongs_to_many :tags`, `has_many :videos, array: true` | `modeling/belongs-to-many`, `modeling/has-many` |
42
+ | Table inheritance | `create_table :child, inherits: :parent` | `expand_records` | `modeling/inherited-tables` |
43
+ | Value computed on demand | | `dynamic_attribute(:name) { }` | `modeling/dynamic-attributes` |
44
+ | Conditional upsert | | `upsert_all(rows, where: ...)` | `modeling/insert-all` |
45
+ | CTE | | `auxiliary_statement :name`, `.with(:name)` | `querying/auxiliary-statements` |
46
+ | First row per group | | `distinct_on(:col)` | `querying/distinct-on` |
47
+ | Histogram | | `buckets(:col, range_or_list)` | `querying/buckets` |
48
+ | Several filtered aggregates | | `segments(name: condition).count` | `querying/segments` |
49
+ | Report rows with no data | | `join_series(range, with: :col)` | `querying/join-series` |
50
+ | Full-text search | `t.search_vector columns:` | `torque_search_for :col`, `.full_text_search` | `querying/full-text-search` |
51
+ | Regexp and array conditions | | `where(title: /x/)` | `querying/predicate-builder` |
52
+ | SQL functions and operators | | `FN.*`, Arel infix methods | `querying/arel` |
53
+ | Several schemas | `create_schema` | `self.schema =` | `experimental/multiple-schemas` |
54
+ | Versioned views, functions, triggers, types | `rails g torque:view` and siblings | | `experimental/versioned-commands` |
55
+ | Join a function, subquery, or row list | | `cross_join(source, as:)` | `experimental/cross-join` |
56
+
57
+ ## Rules
58
+
59
+ - **Enum types** come from Rails' own `create_enum`. Adding values with `add_enum_values` may
60
+ need `disable_ddl_transaction!` in that migration.
61
+ - **Struct classes** subclass `Torque::PostgreSQL::Struct` and are strict by default. Only `jsonb`
62
+ columns can be queried by property. Encrypt properties, never the whole column.
63
+ - **Inherited records** reached through an association come back partial and read-only. Load
64
+ them with `expand_records` before writing to them.
65
+ - **ltree labels** accept dashes only on PostgreSQL 16+. The `ltree.sanitize` setting rewrites
66
+ them, which matters for UUID labels.
67
+ - **`FN`** is `Torque::PostgreSQL::FN`. The app may expose it under a shorter name through
68
+ `arel.expose_function_helper_on`, which the docs write as `PG::Fn`.
69
+ - **Array conditions** use `ANY` and overlaps only when `predicate_builder.handle_array_attributes`
70
+ is on. It is off by default.
@@ -0,0 +1,3 @@
1
+ CREATE OR REPLACE TRIGGER audit_users
2
+ BEFORE INSERT ON users
3
+ FOR EACH ROW EXECUTE FUNCTION count_users();
data/spec/initialize.rb CHANGED
@@ -4,20 +4,32 @@ require_relative '../lib/torque/postgresql/adapter/schema_overrides'
4
4
 
5
5
  require_relative '../lib/torque/postgresql/adapter/oid/box'
6
6
  require_relative '../lib/torque/postgresql/adapter/oid/circle'
7
+ require_relative '../lib/torque/postgresql/adapter/oid/composite'
7
8
  require_relative '../lib/torque/postgresql/adapter/oid/enum'
8
9
  require_relative '../lib/torque/postgresql/adapter/oid/enum_set'
9
10
  require_relative '../lib/torque/postgresql/adapter/oid/interval'
10
11
  require_relative '../lib/torque/postgresql/adapter/oid/line'
12
+ require_relative '../lib/torque/postgresql/adapter/oid/lquery'
13
+ require_relative '../lib/torque/postgresql/adapter/oid/ltree'
11
14
  require_relative '../lib/torque/postgresql/adapter/oid/segment'
15
+ require_relative '../lib/torque/postgresql/adapter/oid/struct'
16
+ require_relative '../lib/torque/postgresql/adapter/oid/struct_list'
17
+ require_relative '../lib/torque/postgresql/adapter/oid/struct_set'
12
18
 
19
+ require_relative '../lib/torque/postgresql/attributes/composite'
13
20
  require_relative '../lib/torque/postgresql/attributes/enum'
14
21
  require_relative '../lib/torque/postgresql/attributes/enum_set'
15
22
  require_relative '../lib/torque/postgresql/attributes/period'
23
+ require_relative '../lib/torque/postgresql/attributes/struct'
16
24
  require_relative '../lib/torque/postgresql/attributes/full_text_search'
25
+ require_relative '../lib/torque/postgresql/attributes/ltree'
26
+ require_relative '../lib/torque/postgresql/attributes/lquery'
17
27
 
18
28
  require_relative '../lib/torque/postgresql/relation/auxiliary_statement'
19
29
  require_relative '../lib/torque/postgresql/relation/join_series'
20
30
  require_relative '../lib/torque/postgresql/relation/buckets'
31
+ require_relative '../lib/torque/postgresql/relation/segments'
32
+ require_relative '../lib/torque/postgresql/relation/cross_join'
21
33
 
22
34
  require_relative '../lib/torque/postgresql/versioned_commands'
23
35
 
@@ -28,11 +40,17 @@ module Torque
28
40
  Attributes::Enum.include_on(ActiveRecord::Base)
29
41
  Attributes::EnumSet.include_on(ActiveRecord::Base)
30
42
  Attributes::Period.include_on(ActiveRecord::Base)
43
+ Attributes::Struct.include_on(ActiveRecord::Base)
31
44
  Attributes::FullTextSearch.include_on(ActiveRecord::Base)
32
45
 
33
46
  Relation.include(Relation::AuxiliaryStatement)
34
47
  Relation.include(Relation::JoinSeries)
35
48
  Relation.include(Relation::Buckets)
49
+ Relation.include(Relation::Segments)
50
+ Relation.include(Relation::CrossJoin)
51
+
52
+ config.cross_join.enabled = true
53
+ ActiveRecord::Base.singleton_class.delegate(:cross_join, to: :all)
36
54
 
37
55
  config.versioned_commands.enabled = true
38
56
  ActiveRecord::Schema::Definition.include(Adapter::Definition)
@@ -40,6 +58,11 @@ module Torque
40
58
  ::Object.const_set('TorqueCTE', AuxiliaryStatement)
41
59
  ::Object.const_set('TorqueRecursiveCTE', AuxiliaryStatement::Recursive)
42
60
 
61
+ config.composite.namespace = ::Object.const_set('Composite', Module.new)
62
+ config.composite.namespace.define_singleton_method(:const_missing) do |name|
63
+ Attributes::Composite.lookup(name)
64
+ end
65
+
43
66
  config.enum.namespace = ::Object.const_set('Enum', Module.new)
44
67
  config.enum.namespace.define_singleton_method(:const_missing) do |name|
45
68
  Attributes::Enum.lookup(name)
@@ -50,8 +73,9 @@ module Torque
50
73
  end
51
74
 
52
75
  ar_type = ActiveRecord::Type
53
- ar_type.register(:enum, Adapter::OID::Enum, adapter: :postgresql)
54
- ar_type.register(:enum_set, Adapter::OID::EnumSet, adapter: :postgresql)
76
+ ar_type.register(:enum, Adapter::OID::Enum, adapter: :postgresql)
77
+ ar_type.register(:enum_set, Adapter::OID::EnumSet, adapter: :postgresql)
78
+ ar_type.register(:composite, Adapter::OID::Composite, adapter: :postgresql)
55
79
 
56
80
  ar_type.register(:box, Adapter::OID::Box, adapter: :postgresql)
57
81
  ar_type.register(:circle, Adapter::OID::Circle, adapter: :postgresql)
@@ -60,6 +84,9 @@ module Torque
60
84
 
61
85
  ar_type.register(:interval, Adapter::OID::Interval, adapter: :postgresql)
62
86
 
87
+ ar_type.register(:ltree, Adapter::OID::Ltree, adapter: :postgresql)
88
+ ar_type.register(:lquery, Adapter::OID::Lquery, adapter: :postgresql)
89
+
63
90
  Arel.build_operations(config.arel.infix_operators)
64
91
 
65
92
  ActiveRecord::Base.connection.torque_load_additional_types
@@ -15,12 +15,24 @@ module Mocks
15
15
  result
16
16
  end
17
17
 
18
+ def capture_executed_queries
19
+ queries = []
20
+ subscriber = ActiveSupport::Notifications.subscribe('sql.active_record') do |*, payload|
21
+ queries << payload[:sql] unless payload[:name] == 'SCHEMA' || payload[:cached]
22
+ end
23
+
24
+ yield
25
+ queries
26
+ ensure
27
+ ActiveSupport::Notifications.unsubscribe(subscriber)
28
+ end
29
+
18
30
  def get_query_with_binds(&block)
19
31
  result = nil
20
32
 
21
33
  original_method = ActiveRecord::Base.connection.method(:raw_execute)
22
34
  original_method.receiver.define_singleton_method(:raw_execute) do |*args, **xargs, &block|
23
- result ||= [args.first, args.third]
35
+ result ||= [args.first, args.third] unless %w[SCHEMA TRANSACTION].include?(args.second)
24
36
  super(*args, **xargs, &block)
25
37
  end
26
38
 
@@ -1,4 +1,4 @@
1
1
  class Author < ActiveRecord::Base
2
- has_many :activities, -> { cast_records }
2
+ has_many :activities, dependent: :destroy
3
3
  has_many :posts
4
4
  end
@@ -1,3 +1,5 @@
1
1
  class Comment < ActiveRecord::Base
2
2
  belongs_to :user
3
+ belongs_to :commentable, polymorphic: true, foreign_key: :video_id,
4
+ foreign_type: :kind, optional: true
3
5
  end
@@ -0,0 +1,2 @@
1
+ class Place < ActiveRecord::Base
2
+ end
@@ -0,0 +1,31 @@
1
+ class Profile < ActiveRecord::Base
2
+ class Settings < Torque::PostgreSQL::Attributes::Struct
3
+ attribute :theme, :string, default: 'light'
4
+ attribute :notifications, :boolean, default: true
5
+ attribute :tags
6
+
7
+ validates :theme, inclusion: { in: %w[light dark] }, allow_nil: true
8
+ end
9
+
10
+ class Bio < Torque::PostgreSQL::Attributes::Struct
11
+ attribute :headline, :string
12
+ attribute :website, :string
13
+ end
14
+
15
+ class Preview < Torque::PostgreSQL::Attributes::Struct
16
+ attribute :label, :string
17
+ attribute :url, :string
18
+
19
+ validates :url, presence: true
20
+ end
21
+
22
+ class Snippet < Torque::PostgreSQL::Attributes::Struct
23
+ attribute :title, :string
24
+ attribute :body, :string
25
+ end
26
+
27
+ struct_for :settings, Settings, delegate: %i[theme]
28
+ struct_for :bio, Bio
29
+ struct_for :previews, Preview, array: true
30
+ struct_for :snippets, Snippet
31
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ ENV['RBS_TEST_TARGET'] ||= 'Torque::PostgreSQL::*,Torque::Generators::*'
4
+ ENV['RBS_TEST_SKIP'] ||= '::Torque::PostgreSQL::Attributes::Lazy,::Torque::PostgreSQL::Adapter::OID::Range::Comparison'
5
+ ENV['RBS_TEST_OPT'] ||= '-I sig'
6
+ ENV['RBS_TEST_LOGLEVEL'] ||= 'warn'
7
+
8
+ require 'rbs/test/setup'
data/spec/schema.rb CHANGED
@@ -10,13 +10,11 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- version = 7
14
-
15
- return if ActiveRecord::Migrator.current_version == version
16
- ActiveRecord::Schema.define(version: version) do
13
+ ActiveRecord::Schema.define do
17
14
  self.verbose = false
18
15
 
19
16
  # These are extensions that must be enabled in order to support this database
17
+ enable_extension "ltree"
20
18
  enable_extension "pgcrypto"
21
19
  enable_extension "plpgsql"
22
20
 
@@ -31,6 +29,27 @@ ActiveRecord::Schema.define(version: version) do
31
29
  create_enum "conflicts", ["valid", "invalid", "untrusted"]
32
30
  create_enum "types", ["A", "B", "C", "D"]
33
31
 
32
+ create_composite_type "address", force: :cascade do |t|
33
+ t.string "street"
34
+ t.string "city"
35
+ t.integer "number"
36
+ t.enum "category", enum_type: :types
37
+ end
38
+
39
+ create_composite_type "full_address", force: :cascade do |t|
40
+ t.composite "base", composite_type: :address
41
+ t.string "country"
42
+ t.date "since"
43
+ t.decimal "rate", precision: 8, scale: 2
44
+ end
45
+
46
+ create_table "places", force: :cascade do |t|
47
+ t.string "name"
48
+ t.composite "home", composite_type: :address
49
+ t.composite "offices", composite_type: :address, array: true
50
+ t.composite "location", composite_type: :full_address
51
+ end
52
+
34
53
  create_table "geometries", force: :cascade do |t|
35
54
  t.point "point"
36
55
  t.line "line"
@@ -65,6 +84,14 @@ ActiveRecord::Schema.define(version: version) do
65
84
  t.datetime "updated_at", null: false
66
85
  end
67
86
 
87
+ create_table "profiles", force: :cascade do |t|
88
+ t.string "name"
89
+ t.jsonb "settings"
90
+ t.json "bio"
91
+ t.jsonb "previews"
92
+ t.jsonb "snippets", array: true
93
+ end
94
+
68
95
  create_table "authors", force: :cascade do |t|
69
96
  t.string "name"
70
97
  t.string "type"
@@ -74,6 +101,9 @@ ActiveRecord::Schema.define(version: version) do
74
101
  create_table "categories", force: :cascade do |t|
75
102
  t.integer "parent_id"
76
103
  t.string "title"
104
+ t.ltree "path", index: { using: :gist }
105
+ t.lquery "pattern"
106
+ t.lquery "patterns", array: true
77
107
  end
78
108
 
79
109
  create_table "texts", force: :cascade do |t|
@@ -128,6 +158,7 @@ ActiveRecord::Schema.define(version: version) do
128
158
  create_table "users", force: :cascade do |t|
129
159
  t.string "name", null: false
130
160
  t.enum "role", enum_type: :roles, default: :visitor
161
+ t.ltree "permissions", array: true
131
162
  t.integer "age"
132
163
  t.datetime "created_at", null: false
133
164
  t.datetime "updated_at", null: false
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require_relative 'rbs_helper' if ENV['RBS_TEST']
1
2
  require 'torque-postgresql'
2
3
  require 'database_cleaner'
3
4
  require 'factory_bot'
@@ -56,7 +56,7 @@ RSpec.describe 'Arel' do
56
56
  it 'does not break the change column default value method' do
57
57
  connection.add_column(:authors, :enabled, :boolean)
58
58
  expect { connection.change_column_default(:authors, :enabled, { from: nil, to: true }) }.not_to raise_error
59
- expect(Author.columns_hash['enabled'].default).to eq('true')
59
+ expect(Author.new.enabled).to be(true)
60
60
  end
61
61
 
62
62
  it 'does not break jsonb' do
@@ -145,12 +145,12 @@ RSpec.describe 'Arel' do
145
145
  condition = Video.arel_table[:tag_ids].contains([1,2]).cast(:bigint, :array)
146
146
  query = Video.all.where(condition).to_sql
147
147
 
148
- expect(query).to include('WHERE "videos"."tag_ids" @> ARRAY[1, 2]::bigint[]')
148
+ expect(query).to include(%{WHERE "videos"."tag_ids" @> '{1,2}'::bigint[]})
149
149
 
150
150
  condition = QuestionSelect.arel_table[:options].overlaps(%w[a b]).cast(:string, :array)
151
151
  query = QuestionSelect.all.where(condition).to_sql
152
152
 
153
- expect(query).to include('"options" && ARRAY[\'a\', \'b\']::string[]')
153
+ expect(query).to include(%{"options" && '{a,b}'::string[]})
154
154
  end
155
155
  end
156
156
  end
@@ -101,6 +101,18 @@ RSpec.describe 'AuxiliaryStatement' do
101
101
  expect(query.send(:bound_attributes).map(&:value_before_type_cast)).to eql([1, 2])
102
102
  end
103
103
 
104
+ it 'collects the bound attributes without a permanent connection' do
105
+ klass.send(:auxiliary_statement, :comments) do |cte|
106
+ cte.query Comment.where(id: 1)
107
+ cte.attributes content: :comment_content
108
+ end
109
+
110
+ expect(ActiveRecord::Base).not_to receive(:connection)
111
+
112
+ query = subject.where(id: 2).with(:comments)
113
+ expect(query.send(:bound_attributes).map(&:value_before_type_cast)).to eql([1, 2])
114
+ end
115
+
104
116
  it 'accepts string as attributes' do
105
117
  klass.send(:auxiliary_statement, :comments) do |cte|
106
118
  cte.query Comment.all