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 +4 -4
- data/lib/generators/migration_patterns.rb +3 -0
- data/lib/generators/vrt/install_generator.rb +12 -0
- data/lib/migration_patterns.rb +20 -3
- data/lib/migration_patterns/configuration.rb +15 -0
- data/lib/migration_patterns/database_helpers.rb +7 -3
- data/lib/migration_patterns/from_union.rb +51 -0
- data/lib/migration_patterns/grant.rb +52 -0
- data/lib/migration_patterns/migration_helpers.rb +13 -13
- data/lib/migration_patterns/sql/union.rb +43 -0
- data/lib/migration_patterns/version.rb +1 -1
- metadata +21 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e3669e424c6540f18368d3314457ac3a3891dc5887db56a75283402913e879e
|
4
|
+
data.tar.gz: 14a223fbb3467497672a14eb8fbaab612e1bef31ed2a0dc494545cd72e53fb6a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e42f11dc8b56b7427795a9da5f49d913010f4ca6b0404167bc708453d6a59382aef0459c395c943cfdea700aba97016bf45f2cb87dec9eaa09e751f366444b01
|
7
|
+
data.tar.gz: 3fd758d0699a22bc236a6e4eeda0ea3970d3ffc539097f52698f4e87b238e68bcd976f875659a0f689c5ae14aff3b7555bd984f7e6a3e2a56578850a70255e25
|
@@ -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
|
data/lib/migration_patterns.rb
CHANGED
@@ -1,12 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
|
5
|
-
|
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
|
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
|
34
|
+
config.username || ENV['USER']
|
35
35
|
end
|
36
36
|
|
37
37
|
def self.database_name
|
38
|
-
config
|
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
|
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
|
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
|
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
|
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
|
-
|
264
|
+
DatabaseHelpers.true_value
|
265
265
|
end
|
266
266
|
|
267
267
|
def false_value
|
268
|
-
|
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
|
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
|
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
|
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 =
|
923
|
-
user =
|
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
|
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
|
-
|
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
|
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.
|
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:
|