ridgepole-view 0.1.0
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 +7 -0
- data/lib/ridgepole/view/delta.rb +58 -0
- data/lib/ridgepole/view/diff.rb +52 -0
- data/lib/ridgepole/view/dsl_parser/context.rb +21 -0
- data/lib/ridgepole/view/dsl_parser.rb +19 -0
- data/lib/ridgepole/view/dumper.rb +27 -0
- data/lib/ridgepole/view/version.rb +7 -0
- data/lib/ridgepole/view/view_definition.rb +28 -0
- data/lib/ridgepole-view.rb +19 -0
- metadata +92 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 69fdd1019a8766f9e1d4518915f2bb35dd8c396e1667f4bd92e731ed6edc3e35
|
|
4
|
+
data.tar.gz: 769a340d1ac813e6c02546124e55caca5bc2d6c964afa95d53bac24a5fa2b005
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1785451331df5dc46efb40feb52878897d953cdba7355c7ad0ed2e7a1b54eefbef2a9c3eb22c84978a2b04df4cfbeca4dc573530489ece91d4e97f74f29a9cc4
|
|
7
|
+
data.tar.gz: 53a60e1f9ccac14ae825a35df3d07d7a6a796514def981b685ae8867050e3767a7bee48ba3354d041aadb7cc2665832a4740ae8d0fa0a5be79028a66f139c8fa
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ridgepole
|
|
4
|
+
module View
|
|
5
|
+
module Delta
|
|
6
|
+
def script
|
|
7
|
+
table_script = super
|
|
8
|
+
view_script = generate_view_script
|
|
9
|
+
[table_script, view_script].map(&:strip).reject(&:empty?).join("\n\n")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def differ?
|
|
13
|
+
!!(super || view_delta_present?)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def view_delta_present?
|
|
19
|
+
vd = @delta[:views]
|
|
20
|
+
vd && (vd[:add]&.any? || vd[:change]&.any? || vd[:delete]&.any?)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def generate_view_script
|
|
24
|
+
buf = StringIO.new
|
|
25
|
+
views = @delta[:views] || {}
|
|
26
|
+
|
|
27
|
+
(views[:delete] || {}).each do |name, attrs|
|
|
28
|
+
append_drop_view(name, attrs, buf)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
(views[:change] || {}).each do |name, change_attrs|
|
|
32
|
+
append_drop_view(name, change_attrs[:from], buf)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
(views[:add] || {}).each do |name, attrs|
|
|
36
|
+
append_create_view(name, attrs, buf)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
(views[:change] || {}).each do |name, change_attrs|
|
|
40
|
+
append_create_view(name, change_attrs[:to], buf)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
buf.string
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def append_create_view(name, attrs, buf)
|
|
47
|
+
mat = attrs[:materialized] ? ", materialized: true" : ""
|
|
48
|
+
sql = attrs[:sql_definition]
|
|
49
|
+
buf.puts "create_view #{name.inspect}, sql_definition: #{sql.inspect}#{mat}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def append_drop_view(name, attrs, buf)
|
|
53
|
+
mat = attrs[:materialized] ? ", materialized: true" : ""
|
|
54
|
+
buf.puts "drop_view #{name.inspect}#{mat}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ridgepole/view/view_definition"
|
|
4
|
+
|
|
5
|
+
module Ridgepole
|
|
6
|
+
module View
|
|
7
|
+
module Diff
|
|
8
|
+
def diff(from, to, options = {})
|
|
9
|
+
from = (from || {}).deep_dup
|
|
10
|
+
to = (to || {}).deep_dup
|
|
11
|
+
|
|
12
|
+
# Ridgepole::Diff#diff iterates all keys in the definition hash as table names,
|
|
13
|
+
# accessing table-specific attributes (:definition, :options) via scan_change etc.
|
|
14
|
+
# The :views data structure differs from tables, so passing it through causes NoMethodError.
|
|
15
|
+
# Extract :views before calling super and handle view diffing separately.
|
|
16
|
+
from_views = from.delete(:views) || {}
|
|
17
|
+
to_views = to.delete(:views) || {}
|
|
18
|
+
|
|
19
|
+
delta = super(from, to, options)
|
|
20
|
+
|
|
21
|
+
view_delta = diff_views(from_views, to_views)
|
|
22
|
+
unless view_delta.values.all?(&:empty?)
|
|
23
|
+
delta.instance_variable_get(:@delta)[:views] = view_delta
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
delta
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def diff_views(from_views, to_views)
|
|
32
|
+
result = { add: {}, change: {}, delete: {} }
|
|
33
|
+
all_names = (from_views.keys + to_views.keys).uniq
|
|
34
|
+
|
|
35
|
+
all_names.each do |name|
|
|
36
|
+
from = from_views[name]
|
|
37
|
+
to = to_views[name]
|
|
38
|
+
|
|
39
|
+
if from.nil? && to
|
|
40
|
+
result[:add][name] = to
|
|
41
|
+
elsif from && to.nil?
|
|
42
|
+
result[:delete][name] = from
|
|
43
|
+
elsif ViewDefinition.changed?(from, to)
|
|
44
|
+
result[:change][name] = { from: from, to: to }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
result
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ridgepole
|
|
4
|
+
module View
|
|
5
|
+
module DSLParser
|
|
6
|
+
module Context
|
|
7
|
+
def create_view(name, sql_definition:, materialized: false)
|
|
8
|
+
name = name.to_s
|
|
9
|
+
@__definition[:views] ||= {}
|
|
10
|
+
|
|
11
|
+
raise "View `#{name}` already defined" if @__definition[:views][name]
|
|
12
|
+
|
|
13
|
+
@__definition[:views][name] = {
|
|
14
|
+
sql_definition: sql_definition,
|
|
15
|
+
materialized: materialized,
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ridgepole
|
|
4
|
+
module View
|
|
5
|
+
module DSLParser
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def check_definition(definition)
|
|
9
|
+
# Ridgepole::DSLParser#check_definition iterates all keys as table names and
|
|
10
|
+
# validates table-specific attributes via check_orphan_index etc.
|
|
11
|
+
# Temporarily remove :views to prevent false validation errors, then restore after super.
|
|
12
|
+
views = definition.delete(:views)
|
|
13
|
+
super(definition)
|
|
14
|
+
ensure
|
|
15
|
+
definition[:views] = views if views
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ridgepole
|
|
4
|
+
module View
|
|
5
|
+
module Dumper
|
|
6
|
+
def dump(&block)
|
|
7
|
+
result = super(&block)
|
|
8
|
+
view_dsl = dump_views
|
|
9
|
+
view_dsl.empty? ? result : [result, view_dsl].join("\n\n")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def dump_views
|
|
15
|
+
views = Scenic.database.views
|
|
16
|
+
views = views.reject { |v| ignored_view?(v.name) }
|
|
17
|
+
views.map(&:to_schema).join("\n")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def ignored_view?(name)
|
|
21
|
+
return false unless @options[:ignore_tables]
|
|
22
|
+
|
|
23
|
+
@options[:ignore_tables].any? { |pattern| pattern =~ name }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ridgepole
|
|
4
|
+
module View
|
|
5
|
+
module ViewDefinition
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
# Normalize SQL for comparison so that semantically identical definitions
|
|
9
|
+
# written differently in the Schemafile vs dumped from PostgreSQL are not
|
|
10
|
+
# treated as changes (which would cause unnecessary drop_view + create_view).
|
|
11
|
+
#
|
|
12
|
+
# e.g. Schemafile: "SELECT name\n FROM users;"
|
|
13
|
+
# PG dump: "select name from users"
|
|
14
|
+
def normalize(sql)
|
|
15
|
+
sql.to_s
|
|
16
|
+
.gsub(/\s+/, " ") # collapse newlines, tabs, multiple spaces into single space
|
|
17
|
+
.gsub(/;\s*\z/, "") # strip trailing semicolons
|
|
18
|
+
.strip # remove leading/trailing whitespace
|
|
19
|
+
.downcase # PG may dump keywords in lowercase
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def changed?(from, to)
|
|
23
|
+
from[:materialized] != to[:materialized] ||
|
|
24
|
+
normalize(from[:sql_definition]) != normalize(to[:sql_definition])
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tsort"
|
|
4
|
+
require "ridgepole"
|
|
5
|
+
require "scenic"
|
|
6
|
+
|
|
7
|
+
require "ridgepole/view/version"
|
|
8
|
+
require "ridgepole/view/view_definition"
|
|
9
|
+
require "ridgepole/view/dsl_parser/context"
|
|
10
|
+
require "ridgepole/view/dsl_parser"
|
|
11
|
+
require "ridgepole/view/dumper"
|
|
12
|
+
require "ridgepole/view/diff"
|
|
13
|
+
require "ridgepole/view/delta"
|
|
14
|
+
|
|
15
|
+
Ridgepole::DSLParser::Context.prepend(Ridgepole::View::DSLParser::Context)
|
|
16
|
+
Ridgepole::DSLParser.prepend(Ridgepole::View::DSLParser)
|
|
17
|
+
Ridgepole::Dumper.prepend(Ridgepole::View::Dumper)
|
|
18
|
+
Ridgepole::Diff.prepend(Ridgepole::View::Diff)
|
|
19
|
+
Ridgepole::Delta.prepend(Ridgepole::View::Delta)
|
metadata
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ridgepole-view
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yuhi-Sato
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: ridgepole
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: scenic
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.5'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.5'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rspec
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '3.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.0'
|
|
54
|
+
description: A Ridgepole plugin that adds database view management using the Scenic
|
|
55
|
+
gem
|
|
56
|
+
executables: []
|
|
57
|
+
extensions: []
|
|
58
|
+
extra_rdoc_files: []
|
|
59
|
+
files:
|
|
60
|
+
- lib/ridgepole-view.rb
|
|
61
|
+
- lib/ridgepole/view/delta.rb
|
|
62
|
+
- lib/ridgepole/view/diff.rb
|
|
63
|
+
- lib/ridgepole/view/dsl_parser.rb
|
|
64
|
+
- lib/ridgepole/view/dsl_parser/context.rb
|
|
65
|
+
- lib/ridgepole/view/dumper.rb
|
|
66
|
+
- lib/ridgepole/view/version.rb
|
|
67
|
+
- lib/ridgepole/view/view_definition.rb
|
|
68
|
+
homepage: https://github.com/Yuhi-Sato/ridgepole-view
|
|
69
|
+
licenses:
|
|
70
|
+
- MIT
|
|
71
|
+
metadata:
|
|
72
|
+
homepage_uri: https://github.com/Yuhi-Sato/ridgepole-view
|
|
73
|
+
source_code_uri: https://github.com/Yuhi-Sato/ridgepole-view
|
|
74
|
+
changelog_uri: https://github.com/Yuhi-Sato/ridgepole-view/blob/main/CHANGELOG.md
|
|
75
|
+
rdoc_options: []
|
|
76
|
+
require_paths:
|
|
77
|
+
- lib
|
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '2.7'
|
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '0'
|
|
88
|
+
requirements: []
|
|
89
|
+
rubygems_version: 3.6.9
|
|
90
|
+
specification_version: 4
|
|
91
|
+
summary: Scenic view support for Ridgepole
|
|
92
|
+
test_files: []
|