migration-patterns 0.0.0.pre.rc1 → 0.0.0.pre.rc3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 865fc1328c592bc00258c83832c12f7ed83757f0202868d8532d7dd30585d57d
4
- data.tar.gz: 88c9961f45cbdce525167682ade3e60a99b25f70dc40a9016313580bbf420237
3
+ metadata.gz: 9e3669e424c6540f18368d3314457ac3a3891dc5887db56a75283402913e879e
4
+ data.tar.gz: 14a223fbb3467497672a14eb8fbaab612e1bef31ed2a0dc494545cd72e53fb6a
5
5
  SHA512:
6
- metadata.gz: 9b6f7ad7f9224be6ead4110071034363e0b9434cc6dc202f9f6b96e2c3962fb15ba0b78f5395ed3a46ef545ad94c895f5f897281d3c77a2b84e6baa121be8242
7
- data.tar.gz: e88d6293f73a04a59c9c46a1ce4fd9fff604d33e9d0d93a8e71310e95d2e043f7b319921821123b4d2674849815d8b430fdfd18e459cbaf897641987a7d9cf25
6
+ metadata.gz: e42f11dc8b56b7427795a9da5f49d913010f4ca6b0404167bc708453d6a59382aef0459c395c943cfdea700aba97016bf45f2cb87dec9eaa09e751f366444b01
7
+ data.tar.gz: 3fd758d0699a22bc236a6e4eeda0ea3970d3ffc539097f52698f4e87b238e68bcd976f875659a0f689c5ae14aff3b7555bd984f7e6a3e2a56578850a70255e25
@@ -0,0 +1,3 @@
1
+ # Pre-warm the in-memory store of these class instance vars when we launch the
2
+ # server. Prevents unnecessary file I/O per-request.
3
+ VRT.reload!
@@ -0,0 +1,12 @@
1
+ require 'rails/generators/base'
2
+
3
+ module Vrt
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root(File.expand_path(File.dirname(__dir__)))
7
+ def create_initializer_file
8
+ copy_file '../vrt.rb', 'config/initializers/vrt.rb'
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,12 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'migration_patterns/database_helpers'
4
- require 'migration_patterns/migration_helpers'
5
- # require 'migration_patterns/errors'
3
+ require 'active_record'
4
+
5
+ require 'migration_patterns/configuration'
6
6
 
7
7
  # require 'date'
8
8
  # require 'json'
9
9
  # require 'pathname'
10
10
 
11
11
  module MigrationPatterns
12
+ def self.configure
13
+ @configuration = MigrationPatterns::Configuration.new
14
+
15
+ yield configuration if block_given?
16
+
17
+ configuration
18
+ end
19
+
20
+ def self.configuration
21
+ @configuration || configure
22
+ end
23
+
24
+ require 'migration_patterns/sql/union'
25
+ require 'migration_patterns/from_union'
26
+ require 'migration_patterns/database_helpers'
27
+ require 'migration_patterns/grant'
28
+ require 'migration_patterns/migration_helpers'
12
29
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MigrationPatterns
4
+ class Configuration
5
+ attr_accessor :adapter, :database, :username
6
+
7
+ private
8
+
9
+ def initialize
10
+ @adapter = 'postgresql'
11
+ @database = 'db_name'
12
+ @username = 'postgres'
13
+ end
14
+ end
15
+ end
@@ -3,7 +3,7 @@
3
3
  module MigrationPatterns
4
4
  module DatabaseHelpers
5
5
  def self.adapter_name
6
- config['adapter']
6
+ config.adapter
7
7
  end
8
8
 
9
9
  def self.postgresql?
@@ -31,11 +31,15 @@ module MigrationPatterns
31
31
  end
32
32
 
33
33
  def self.username
34
- config['username'] || ENV['USER']
34
+ config.username || ENV['USER']
35
35
  end
36
36
 
37
37
  def self.database_name
38
- config['database']
38
+ config.database
39
+ end
40
+
41
+ def self.config
42
+ MigrationPatterns.configuration
39
43
  end
40
44
  end
41
45
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MigrationPatterns
4
+ module FromUnion
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ # Produces a query that uses a FROM to select data using a UNION.
9
+ #
10
+ # Using a FROM for a UNION has in the past lead to better query plans. As
11
+ # such, we generally recommend this pattern instead of using a WHERE IN.
12
+ #
13
+ # Example:
14
+ # users = User.from_union([User.where(id: 1), User.where(id: 2)])
15
+ #
16
+ # This would produce the following SQL query:
17
+ #
18
+ # SELECT *
19
+ # FROM (
20
+ # SELECT *
21
+ # FROM users
22
+ # WHERE id = 1
23
+ #
24
+ # UNION
25
+ #
26
+ # SELECT *
27
+ # FROM users
28
+ # WHERE id = 2
29
+ # ) users;
30
+ #
31
+ # members - An Array of ActiveRecord::Relation objects to use in the UNION.
32
+ #
33
+ # remove_duplicates - A boolean indicating if duplicate entries should be
34
+ # removed. Defaults to true.
35
+ #
36
+ # alias_as - The alias to use for the sub query. Defaults to the name of the
37
+ # table of the current model.
38
+ def from_union(members, remove_duplicates: true, alias_as: table_name)
39
+ union = MigrationPatterns::SQL::Union
40
+ .new(members, remove_duplicates: remove_duplicates)
41
+ .to_sql
42
+
43
+ # This pattern is necessary as a bug in Rails 4 can cause the use of
44
+ # `from("string here").includes(:foo)` to break ActiveRecord. This is
45
+ # fixed in https://github.com/rails/rails/pull/25374, which is released as
46
+ # part of Rails 5.
47
+ from([Arel.sql("(#{union}) #{alias_as}")])
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MigrationPatterns
4
+ # Model that can be used for querying permissions of a SQL user.
5
+ class Grant < ::ActiveRecord::Base
6
+ include MigrationPatterns::FromUnion
7
+
8
+ self.table_name =
9
+ if ::MigrationPatterns::DatabaseHelpers.postgresql?
10
+ 'information_schema.role_table_grants'
11
+ else
12
+ 'information_schema.schema_privileges'
13
+ end
14
+
15
+ # Returns true if the current user can create and execute triggers on the
16
+ # given table.
17
+ def self.create_and_execute_trigger?(table)
18
+ if DatabaseHelpers.postgresql?
19
+ # We _must not_ use quote_table_name as this will produce double
20
+ # quotes on PostgreSQL and for "has_table_privilege" we need single
21
+ # quotes.
22
+ quoted_table = connection.quote(table)
23
+
24
+ begin
25
+ from(nil)
26
+ .pluck("has_table_privilege(#{quoted_table}, 'TRIGGER')")
27
+ .first
28
+ rescue ActiveRecord::StatementInvalid
29
+ # This error is raised when using a non-existing table name. In this
30
+ # case we just want to return false as a user technically can't
31
+ # create triggers for such a table.
32
+ false
33
+ end
34
+ else
35
+ queries = [
36
+ Grant.select(1)
37
+ .from('information_schema.user_privileges')
38
+ .where("PRIVILEGE_TYPE = 'SUPER'")
39
+ .where("GRANTEE = CONCAT('\\'', REPLACE(CURRENT_USER(), '@', '\\'@\\''), '\\'')"),
40
+
41
+ Grant.select(1)
42
+ .from('information_schema.schema_privileges')
43
+ .where("PRIVILEGE_TYPE = 'TRIGGER'")
44
+ .where('TABLE_SCHEMA = ?', DatabaseHelpers.database_name)
45
+ .where("GRANTEE = CONCAT('\\'', REPLACE(CURRENT_USER(), '@', '\\'@\\''), '\\'')")
46
+ ]
47
+
48
+ Grant.from_union(queries, alias_as: 'privs').any?
49
+ end
50
+ end
51
+ end
52
+ end
@@ -55,7 +55,7 @@ module MigrationPatterns
55
55
  'in the body of your migration class'
56
56
  end
57
57
 
58
- options = options.merge(algorithm: :concurrently) if Database.postgresql?
58
+ options = options.merge(algorithm: :concurrently) if DatabaseHelpers.postgresql?
59
59
 
60
60
  if index_exists?(table_name, column_name, options)
61
61
  Rails.logger.warn "Index not created because it already exists (this may be due to an aborted migration or similar): table_name: #{table_name}, column_name: #{column_name}"
@@ -125,7 +125,7 @@ module MigrationPatterns
125
125
 
126
126
  # Only available on Postgresql >= 9.2
127
127
  def supports_drop_index_concurrently?
128
- return false unless Database.postgresql?
128
+ return false unless DatabaseHelpers.postgresql?
129
129
 
130
130
  version = select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i
131
131
 
@@ -150,7 +150,7 @@ module MigrationPatterns
150
150
  # While MySQL does allow disabling of foreign keys it has no equivalent
151
151
  # of PostgreSQL's "VALIDATE CONSTRAINT". As a result we'll just fall
152
152
  # back to the normal foreign key procedure.
153
- if Database.mysql?
153
+ if DatabaseHelpers.mysql?
154
154
  if foreign_key_exists?(source, target, column: column)
155
155
  Rails.logger.warn 'Foreign key not created because it exists already ' \
156
156
  '(this may be due to an aborted migration or similar): ' \
@@ -230,7 +230,7 @@ module MigrationPatterns
230
230
  # or `RESET ALL` is executed
231
231
  def disable_statement_timeout
232
232
  # bypass disabled_statement logic when not using postgres, but still execute block when one is given
233
- unless Database.postgresql?
233
+ unless DatabaseHelpers.postgresql?
234
234
  yield if block_given?
235
235
 
236
236
  return
@@ -261,11 +261,11 @@ module MigrationPatterns
261
261
  end
262
262
 
263
263
  def true_value
264
- Database.true_value
264
+ DatabaseHelpers.true_value
265
265
  end
266
266
 
267
267
  def false_value
268
- Database.false_value
268
+ DatabaseHelpers.false_value
269
269
  end
270
270
 
271
271
  # Updates the value of a column in batches.
@@ -482,7 +482,7 @@ module MigrationPatterns
482
482
  quoted_old = quote_column_name(old_column)
483
483
  quoted_new = quote_column_name(new_column)
484
484
 
485
- if Database.postgresql?
485
+ if DatabaseHelpers.postgresql?
486
486
  install_rename_triggers_for_postgresql(trigger_name, quoted_table,
487
487
  quoted_old, quoted_new)
488
488
  else
@@ -531,7 +531,7 @@ module MigrationPatterns
531
531
 
532
532
  check_trigger_permissions!(table)
533
533
 
534
- if Database.postgresql?
534
+ if DatabaseHelpers.postgresql?
535
535
  remove_rename_triggers_for_postgresql(table, trigger_name)
536
536
  else
537
537
  remove_rename_triggers_for_mysql(trigger_name)
@@ -880,7 +880,7 @@ module MigrationPatterns
880
880
  quoted_pattern = Arel::Nodes::Quoted.new(pattern.to_s)
881
881
  quoted_replacement = Arel::Nodes::Quoted.new(replacement.to_s)
882
882
 
883
- if Database.mysql?
883
+ if DatabaseHelpers.mysql?
884
884
  locate = Arel::Nodes::NamedFunction
885
885
  .new('locate', [quoted_pattern, column])
886
886
  insert_in_place = Arel::Nodes::NamedFunction
@@ -919,8 +919,8 @@ module MigrationPatterns
919
919
 
920
920
  def check_trigger_permissions!(table)
921
921
  unless Grant.create_and_execute_trigger?(table)
922
- dbname = Database.database_name
923
- user = Database.username
922
+ dbname = DatabaseHelpers.database_name
923
+ user = DatabaseHelpers.username
924
924
 
925
925
  raise <<~EOF
926
926
  Your database user is not allowed to create, drop, or execute triggers on the
@@ -1052,7 +1052,7 @@ module MigrationPatterns
1052
1052
  # does not find indexes without passing a column name.
1053
1053
  if indexes(table).map(&:name).include?(index.to_s)
1054
1054
  true
1055
- elsif Database.postgresql?
1055
+ elsif DatabaseHelpers.postgresql?
1056
1056
  postgres_exists_by_name?(table, index)
1057
1057
  else
1058
1058
  false
@@ -1072,7 +1072,7 @@ module MigrationPatterns
1072
1072
  end
1073
1073
 
1074
1074
  def mysql_compatible_index_length
1075
- Database.mysql? ? 20 : nil
1075
+ DatabaseHelpers.mysql? ? 20 : nil
1076
1076
  end
1077
1077
  end
1078
1078
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MigrationPatterns
4
+ module SQL
5
+ # Class for building SQL UNION statements.
6
+ #
7
+ # ORDER BYs are dropped from the relations as the final sort order is not
8
+ # guaranteed any way.
9
+ #
10
+ # Example usage:
11
+ #
12
+ # union = Gitlab::SQL::Union.new([user.personal_projects, user.projects])
13
+ # sql = union.to_sql
14
+ #
15
+ # Project.where("id IN (#{sql})")
16
+ class Union
17
+ def initialize(relations, remove_duplicates: true)
18
+ @relations = relations
19
+ @remove_duplicates = remove_duplicates
20
+ end
21
+
22
+ def to_sql
23
+ # Some relations may include placeholders for prepared statements, these
24
+ # aren't incremented properly when joining relations together this way.
25
+ # By using "unprepared_statements" we remove the usage of placeholders
26
+ # (thus fixing this problem), at a slight performance cost.
27
+ fragments = ActiveRecord::Base.connection.unprepared_statement do
28
+ @relations.map { |rel| rel.reorder(nil).to_sql }.reject(&:blank?)
29
+ end
30
+
31
+ if fragments.any?
32
+ fragments.join("\n#{union_keyword}\n")
33
+ else
34
+ 'NULL'
35
+ end
36
+ end
37
+
38
+ def union_keyword
39
+ @remove_duplicates ? 'UNION' : 'UNION ALL'
40
+ end
41
+ end
42
+ end
43
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module MigrationPatterns
4
4
  module Version
5
- STRING = '0.0.0-rc1'
5
+ STRING = '0.0.0-rc3'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: migration-patterns
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0.pre.rc1
4
+ version: 0.0.0.pre.rc3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam David
@@ -95,6 +95,20 @@ dependencies:
95
95
  - - ">="
96
96
  - !ruby/object:Gem::Version
97
97
  version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: activerecord
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
98
112
  description:
99
113
  email:
100
114
  - adamrdavid@gmail.com
@@ -102,10 +116,16 @@ executables: []
102
116
  extensions: []
103
117
  extra_rdoc_files: []
104
118
  files:
119
+ - lib/generators/migration_patterns.rb
120
+ - lib/generators/vrt/install_generator.rb
105
121
  - lib/migration_patterns.rb
106
122
  - lib/migration_patterns/background_job.rb
123
+ - lib/migration_patterns/configuration.rb
107
124
  - lib/migration_patterns/database_helpers.rb
125
+ - lib/migration_patterns/from_union.rb
126
+ - lib/migration_patterns/grant.rb
108
127
  - lib/migration_patterns/migration_helpers.rb
128
+ - lib/migration_patterns/sql/union.rb
109
129
  - lib/migration_patterns/version.rb
110
130
  homepage: https://github.com/adamrdavid/migration-patterns
111
131
  licenses: