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

Sign up to get free protection for your applications and to get access to all the features.
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: