chrono_model 1.2.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +19 -20
- data/README.md +62 -40
- data/lib/active_record/connection_adapters/chronomodel_adapter.rb +17 -11
- data/lib/active_record/tasks/chronomodel_database_tasks.rb +64 -23
- data/lib/chrono_model/adapter/ddl.rb +168 -153
- data/lib/chrono_model/adapter/indexes.rb +99 -94
- data/lib/chrono_model/adapter/migrations.rb +81 -104
- data/lib/chrono_model/adapter/migrations_modules/legacy.rb +41 -0
- data/lib/chrono_model/adapter/migrations_modules/stable.rb +41 -0
- data/lib/chrono_model/adapter/tsrange.rb +20 -5
- data/lib/chrono_model/adapter/upgrade.rb +89 -91
- data/lib/chrono_model/adapter.rb +64 -31
- data/lib/chrono_model/chrono.rb +17 -0
- data/lib/chrono_model/conversions.rb +15 -9
- data/lib/chrono_model/db_console.rb +9 -0
- data/lib/chrono_model/json.rb +9 -6
- data/lib/chrono_model/patches/as_of_time_holder.rb +2 -2
- data/lib/chrono_model/patches/as_of_time_relation.rb +2 -2
- data/lib/chrono_model/patches/association.rb +15 -12
- data/lib/chrono_model/patches/batches.rb +17 -0
- data/lib/chrono_model/patches/db_console.rb +20 -4
- data/lib/chrono_model/patches/join_node.rb +4 -4
- data/lib/chrono_model/patches/preloader.rb +41 -11
- data/lib/chrono_model/patches/relation.rb +53 -8
- data/lib/chrono_model/patches.rb +3 -1
- data/lib/chrono_model/railtie.rb +29 -24
- data/lib/chrono_model/time_gate.rb +3 -3
- data/lib/chrono_model/time_machine/history_model.rb +65 -31
- data/lib/chrono_model/time_machine/time_query.rb +65 -49
- data/lib/chrono_model/time_machine/timeline.rb +52 -28
- data/lib/chrono_model/time_machine.rb +66 -25
- data/lib/chrono_model/utilities.rb +3 -3
- data/lib/chrono_model/version.rb +3 -1
- data/lib/chrono_model.rb +31 -36
- metadata +39 -136
- data/.gitignore +0 -21
- data/.rspec +0 -2
- data/.travis.yml +0 -41
- data/Gemfile +0 -4
- data/README.sql +0 -161
- data/Rakefile +0 -25
- data/chrono_model.gemspec +0 -33
- data/gemfiles/rails_5.0.gemfile +0 -6
- data/gemfiles/rails_5.1.gemfile +0 -6
- data/gemfiles/rails_5.2.gemfile +0 -6
- data/spec/aruba/dbconsole_spec.rb +0 -25
- data/spec/aruba/fixtures/database_with_default_username_and_password.yml +0 -14
- data/spec/aruba/fixtures/database_without_username_and_password.yml +0 -11
- data/spec/aruba/fixtures/empty_structure.sql +0 -27
- data/spec/aruba/fixtures/migrations/56/20160812190335_create_impressions.rb +0 -10
- data/spec/aruba/fixtures/migrations/56/20171115195229_add_temporal_extension_to_impressions.rb +0 -10
- data/spec/aruba/fixtures/railsapp/config/application.rb +0 -17
- data/spec/aruba/fixtures/railsapp/config/boot.rb +0 -5
- data/spec/aruba/fixtures/railsapp/config/environments/development.rb +0 -38
- data/spec/aruba/migrations_spec.rb +0 -48
- data/spec/aruba/rake_task_spec.rb +0 -71
- data/spec/chrono_model/adapter/base_spec.rb +0 -157
- data/spec/chrono_model/adapter/ddl_spec.rb +0 -243
- data/spec/chrono_model/adapter/indexes_spec.rb +0 -72
- data/spec/chrono_model/adapter/migrations_spec.rb +0 -312
- data/spec/chrono_model/conversions_spec.rb +0 -43
- data/spec/chrono_model/history_models_spec.rb +0 -32
- data/spec/chrono_model/json_ops_spec.rb +0 -59
- data/spec/chrono_model/time_machine/as_of_spec.rb +0 -188
- data/spec/chrono_model/time_machine/changes_spec.rb +0 -50
- data/spec/chrono_model/time_machine/counter_cache_race_spec.rb +0 -46
- data/spec/chrono_model/time_machine/default_scope_spec.rb +0 -37
- data/spec/chrono_model/time_machine/history_spec.rb +0 -104
- data/spec/chrono_model/time_machine/keep_cool_spec.rb +0 -27
- data/spec/chrono_model/time_machine/manipulations_spec.rb +0 -84
- data/spec/chrono_model/time_machine/model_identification_spec.rb +0 -46
- data/spec/chrono_model/time_machine/sequence_spec.rb +0 -74
- data/spec/chrono_model/time_machine/sti_spec.rb +0 -100
- data/spec/chrono_model/time_machine/time_query_spec.rb +0 -261
- data/spec/chrono_model/time_machine/timeline_spec.rb +0 -63
- data/spec/chrono_model/time_machine/timestamps_spec.rb +0 -43
- data/spec/chrono_model/time_machine/transactions_spec.rb +0 -69
- data/spec/config.travis.yml +0 -5
- data/spec/config.yml.example +0 -9
- data/spec/spec_helper.rb +0 -33
- data/spec/support/adapter/helpers.rb +0 -53
- data/spec/support/adapter/structure.rb +0 -44
- data/spec/support/aruba.rb +0 -44
- data/spec/support/connection.rb +0 -70
- data/spec/support/matchers/base.rb +0 -56
- data/spec/support/matchers/column.rb +0 -99
- data/spec/support/matchers/function.rb +0 -79
- data/spec/support/matchers/index.rb +0 -69
- data/spec/support/matchers/schema.rb +0 -39
- data/spec/support/matchers/table.rb +0 -275
- data/spec/support/time_machine/helpers.rb +0 -47
- data/spec/support/time_machine/structure.rb +0 -111
- data/sql/json_ops.sql +0 -56
- data/sql/uninstall-json_ops.sql +0 -24
@@ -1,56 +0,0 @@
|
|
1
|
-
module ChronoTest::Matchers
|
2
|
-
class Base
|
3
|
-
include ActiveRecord::Sanitization::ClassMethods
|
4
|
-
|
5
|
-
attr_reader :table
|
6
|
-
|
7
|
-
def matches?(table)
|
8
|
-
table = table.table_name if table.respond_to?(:table_name)
|
9
|
-
@table = table
|
10
|
-
end
|
11
|
-
private :matches? # This is an abstract class
|
12
|
-
|
13
|
-
def failure_message_for_should_not
|
14
|
-
failure_message_for_should.gsub(/to /, 'to not ')
|
15
|
-
end
|
16
|
-
|
17
|
-
protected
|
18
|
-
|
19
|
-
def connection
|
20
|
-
ChronoTest.connection
|
21
|
-
end
|
22
|
-
|
23
|
-
def temporal_schema
|
24
|
-
ChronoModel::Adapter::TEMPORAL_SCHEMA
|
25
|
-
end
|
26
|
-
|
27
|
-
def history_schema
|
28
|
-
ChronoModel::Adapter::HISTORY_SCHEMA
|
29
|
-
end
|
30
|
-
|
31
|
-
def public_schema
|
32
|
-
'public'
|
33
|
-
end
|
34
|
-
|
35
|
-
def select_value(sql, binds, name = nil)
|
36
|
-
result = exec_query(sql, binds, name || 'select_value')
|
37
|
-
result.rows.first.try(:[], 0)
|
38
|
-
end
|
39
|
-
|
40
|
-
def select_values(sql, binds, name = nil)
|
41
|
-
result = exec_query(sql, binds, name || 'select_values')
|
42
|
-
result.rows.map(&:first)
|
43
|
-
end
|
44
|
-
|
45
|
-
def select_rows(sql, binds, name = nil)
|
46
|
-
exec_query(sql, binds, name || 'select_rows').rows
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
def exec_query(sql, binds, name)
|
51
|
-
sql = sanitize_sql_array([ sql, *Array.wrap(binds) ])
|
52
|
-
connection.exec_query(sql, name)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
end
|
@@ -1,99 +0,0 @@
|
|
1
|
-
module ChronoTest::Matchers
|
2
|
-
|
3
|
-
module Column
|
4
|
-
class HaveColumns < ChronoTest::Matchers::Base
|
5
|
-
def initialize(columns, schema = 'public')
|
6
|
-
@columns = columns
|
7
|
-
@schema = schema
|
8
|
-
end
|
9
|
-
|
10
|
-
def description
|
11
|
-
'have columns'
|
12
|
-
end
|
13
|
-
|
14
|
-
def matches?(table)
|
15
|
-
super(table)
|
16
|
-
|
17
|
-
@matches = @columns.inject({}) do |h, (name, type)|
|
18
|
-
h.update([name, type] => has_column?(name, type))
|
19
|
-
end
|
20
|
-
|
21
|
-
@matches.values.all?
|
22
|
-
end
|
23
|
-
|
24
|
-
def failure_message
|
25
|
-
message_matches("expected #{@schema}.#{table} to have")
|
26
|
-
end
|
27
|
-
|
28
|
-
def failure_message_when_negated
|
29
|
-
message_matches("expected #{@schema}.#{table} to not have")
|
30
|
-
end
|
31
|
-
|
32
|
-
protected
|
33
|
-
def has_column?(name, type)
|
34
|
-
column_type(name) == [name, type]
|
35
|
-
end
|
36
|
-
|
37
|
-
def column_type(name)
|
38
|
-
table = "#{@schema}.#{self.table}"
|
39
|
-
|
40
|
-
select_rows(<<-SQL, [table, name], 'Check column').first
|
41
|
-
SELECT attname, FORMAT_TYPE(atttypid, atttypmod)
|
42
|
-
FROM pg_attribute
|
43
|
-
WHERE attrelid = ?::regclass::oid
|
44
|
-
AND attname = ?
|
45
|
-
SQL
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
def message_matches(message)
|
50
|
-
(message << ' ').tap do |message|
|
51
|
-
message << @matches.map do |(name, type), match|
|
52
|
-
"a #{name}(#{type}) column" unless match
|
53
|
-
end.compact.to_sentence
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def have_columns(*args)
|
59
|
-
HaveColumns.new(*args)
|
60
|
-
end
|
61
|
-
|
62
|
-
|
63
|
-
class HaveTemporalColumns < HaveColumns
|
64
|
-
def initialize(columns)
|
65
|
-
super(columns, temporal_schema)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def have_temporal_columns(*args)
|
70
|
-
HaveTemporalColumns.new(*args)
|
71
|
-
end
|
72
|
-
|
73
|
-
|
74
|
-
class HaveHistoryColumns < HaveColumns
|
75
|
-
def initialize(columns)
|
76
|
-
super(columns, history_schema)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def have_history_columns(*args)
|
81
|
-
HaveHistoryColumns.new(*args)
|
82
|
-
end
|
83
|
-
|
84
|
-
|
85
|
-
class HaveHistoryExtraColumns < HaveColumns
|
86
|
-
def initialize
|
87
|
-
super([
|
88
|
-
['validity', 'tsrange'],
|
89
|
-
['recorded_at', 'timestamp without time zone'],
|
90
|
-
['hid', 'integer']
|
91
|
-
], history_schema)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def have_history_extra_columns
|
96
|
-
HaveHistoryExtraColumns.new
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
@@ -1,79 +0,0 @@
|
|
1
|
-
module ChronoTest::Matchers
|
2
|
-
|
3
|
-
module Column
|
4
|
-
class HaveFunctions < ChronoTest::Matchers::Base
|
5
|
-
def initialize(functions, schema = 'public')
|
6
|
-
@functions = functions
|
7
|
-
@schema = schema
|
8
|
-
end
|
9
|
-
|
10
|
-
def description
|
11
|
-
'have functions'
|
12
|
-
end
|
13
|
-
|
14
|
-
def matches?(table)
|
15
|
-
super(table)
|
16
|
-
|
17
|
-
@matches = @functions.inject({}) do |h, name|
|
18
|
-
h.update(name => has_function?(name))
|
19
|
-
end
|
20
|
-
|
21
|
-
@matches.values.all?
|
22
|
-
end
|
23
|
-
|
24
|
-
def failure_message
|
25
|
-
message_matches("expected #{@schema}.#{table} to have")
|
26
|
-
end
|
27
|
-
|
28
|
-
def failure_message_when_negated
|
29
|
-
message_matches("expected #{@schema}.#{table} to not have")
|
30
|
-
end
|
31
|
-
|
32
|
-
protected
|
33
|
-
def has_function?(name)
|
34
|
-
select_value(<<-SQL, [name], 'Check function') == true
|
35
|
-
SELECT EXISTS(
|
36
|
-
SELECT 1
|
37
|
-
FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n
|
38
|
-
WHERE p.pronamespace = n.oid
|
39
|
-
AND n.nspname = 'public'
|
40
|
-
AND p.proname = ?
|
41
|
-
)
|
42
|
-
SQL
|
43
|
-
end
|
44
|
-
|
45
|
-
private
|
46
|
-
def message_matches(message)
|
47
|
-
(message << ' ').tap do |message|
|
48
|
-
message << @matches.map do |name, match|
|
49
|
-
"a #{name} function"
|
50
|
-
end.compact.to_sentence
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def have_functions(*args)
|
56
|
-
HaveFunctions.new(*args)
|
57
|
-
end
|
58
|
-
|
59
|
-
class HaveHistoryFunctions < HaveFunctions
|
60
|
-
def initialize
|
61
|
-
@function_templates = [
|
62
|
-
'chronomodel_%s_insert',
|
63
|
-
'chronomodel_%s_update',
|
64
|
-
'chronomodel_%s_delete',
|
65
|
-
]
|
66
|
-
end
|
67
|
-
|
68
|
-
def matches?(table)
|
69
|
-
@functions = @function_templates.map {|t| t % [table] }
|
70
|
-
|
71
|
-
super(table)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def have_history_functions
|
76
|
-
HaveHistoryFunctions.new
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
@@ -1,69 +0,0 @@
|
|
1
|
-
module ChronoTest::Matchers
|
2
|
-
|
3
|
-
module Index
|
4
|
-
class HaveIndex < ChronoTest::Matchers::Base
|
5
|
-
attr_reader :name, :columns, :schema
|
6
|
-
|
7
|
-
def initialize(name, columns, schema = 'public')
|
8
|
-
@name = name
|
9
|
-
@columns = columns.sort
|
10
|
-
@schema = schema
|
11
|
-
end
|
12
|
-
|
13
|
-
def description
|
14
|
-
'have index'
|
15
|
-
end
|
16
|
-
|
17
|
-
def matches?(table)
|
18
|
-
super(table)
|
19
|
-
|
20
|
-
select_values(<<-SQL, [ table, name, schema ], 'Check index') == columns
|
21
|
-
SELECT a.attname
|
22
|
-
FROM pg_class t
|
23
|
-
JOIN pg_index d ON t.oid = d.indrelid
|
24
|
-
JOIN pg_class i ON i.oid = d.indexrelid
|
25
|
-
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(d.indkey)
|
26
|
-
WHERE i.relkind = 'i'
|
27
|
-
AND t.relname = ?
|
28
|
-
AND i.relname = ?
|
29
|
-
AND i.relnamespace = (
|
30
|
-
SELECT oid FROM pg_namespace WHERE nspname = ?
|
31
|
-
)
|
32
|
-
ORDER BY a.attname
|
33
|
-
SQL
|
34
|
-
end
|
35
|
-
|
36
|
-
def failure_message
|
37
|
-
"expected #{schema}.#{table} to have a #{name} index on #{columns}"
|
38
|
-
end
|
39
|
-
|
40
|
-
def failure_message_when_negated
|
41
|
-
"expected #{schema}.#{table} to not have a #{name} index on #{columns}"
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def have_index(*args)
|
46
|
-
HaveIndex.new(*args)
|
47
|
-
end
|
48
|
-
|
49
|
-
class HaveTemporalIndex < HaveIndex
|
50
|
-
def initialize(name, columns)
|
51
|
-
super(name, columns, temporal_schema)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def have_temporal_index(*args)
|
56
|
-
HaveTemporalIndex.new(*args)
|
57
|
-
end
|
58
|
-
|
59
|
-
class HaveHistoryIndex < HaveIndex
|
60
|
-
def initialize(name, columns)
|
61
|
-
super(name, columns, history_schema)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def have_history_index(*args)
|
66
|
-
HaveHistoryIndex.new(*args)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
@@ -1,39 +0,0 @@
|
|
1
|
-
require 'support/matchers/base'
|
2
|
-
|
3
|
-
module ChronoTest::Matchers
|
4
|
-
|
5
|
-
module Schema
|
6
|
-
class BeInSchema < ChronoTest::Matchers::Base
|
7
|
-
|
8
|
-
def initialize(expected)
|
9
|
-
@expected = expected
|
10
|
-
@expected = '"$user", public' if @expected == :default
|
11
|
-
end
|
12
|
-
|
13
|
-
def description
|
14
|
-
'be in schema'
|
15
|
-
end
|
16
|
-
|
17
|
-
def failure_message
|
18
|
-
"expected to be in schema #@expected, but was in #@current"
|
19
|
-
end
|
20
|
-
|
21
|
-
def failure_message_when_negated
|
22
|
-
"expected to be in schema #@current, but was in #@expected"
|
23
|
-
end
|
24
|
-
|
25
|
-
def matches?(*)
|
26
|
-
@current = select_value(<<-SQL, [], 'Current schema')
|
27
|
-
SHOW search_path
|
28
|
-
SQL
|
29
|
-
|
30
|
-
@current == @expected
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def be_in_schema(schema)
|
35
|
-
BeInSchema.new(schema)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
end
|
@@ -1,275 +0,0 @@
|
|
1
|
-
require 'support/matchers/base'
|
2
|
-
|
3
|
-
module ChronoTest::Matchers
|
4
|
-
|
5
|
-
module Table
|
6
|
-
class Base < ChronoTest::Matchers::Base
|
7
|
-
|
8
|
-
protected
|
9
|
-
# Database statements
|
10
|
-
#
|
11
|
-
def relation_exists?(options)
|
12
|
-
schema = options[:in]
|
13
|
-
kind = options[:kind] == :view ? 'v' : 'r'
|
14
|
-
|
15
|
-
select_value(<<-SQL, [ table, schema ], 'Check table exists') == true
|
16
|
-
SELECT EXISTS (
|
17
|
-
SELECT 1
|
18
|
-
FROM pg_class c
|
19
|
-
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
20
|
-
WHERE c.relkind = '#{kind}'
|
21
|
-
AND c.relname = ?
|
22
|
-
AND n.nspname = ?
|
23
|
-
)
|
24
|
-
SQL
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
# ##################################################################
|
29
|
-
# Checks that a table exists in the Public schema
|
30
|
-
#
|
31
|
-
class HavePublicBacking < Base
|
32
|
-
def matches?(table)
|
33
|
-
super(table)
|
34
|
-
|
35
|
-
relation_exists? :in => public_schema
|
36
|
-
end
|
37
|
-
|
38
|
-
def description
|
39
|
-
'be in the public schema'
|
40
|
-
end
|
41
|
-
|
42
|
-
def failure_message
|
43
|
-
"expected #{table} to exist in the #{public_schema} schema"
|
44
|
-
end
|
45
|
-
|
46
|
-
def failure_message_when_negated
|
47
|
-
"expected #{table} to not exist in the #{public_schema} schema"
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def have_public_backing
|
52
|
-
HavePublicBacking.new
|
53
|
-
end
|
54
|
-
|
55
|
-
|
56
|
-
# ##################################################################
|
57
|
-
# Checks that a table exists in the Temporal schema
|
58
|
-
#
|
59
|
-
class HaveTemporalBacking < Base
|
60
|
-
def matches?(table)
|
61
|
-
super(table)
|
62
|
-
|
63
|
-
relation_exists? :in => temporal_schema
|
64
|
-
end
|
65
|
-
|
66
|
-
def description
|
67
|
-
'be in the temporal schema'
|
68
|
-
end
|
69
|
-
|
70
|
-
def failure_message
|
71
|
-
"expected #{table} to exist in the #{temporal_schema} schema"
|
72
|
-
end
|
73
|
-
|
74
|
-
def failure_message_when_negated
|
75
|
-
"expected #{table} to not exist in the #{temporal_schema} schema"
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def have_temporal_backing
|
80
|
-
HaveTemporalBacking.new
|
81
|
-
end
|
82
|
-
|
83
|
-
|
84
|
-
# ##################################################################
|
85
|
-
# Checks that a table exists in the History schema and inherits from
|
86
|
-
# the one in the Temporal schema
|
87
|
-
#
|
88
|
-
class HaveHistoryBacking < Base
|
89
|
-
def matches?(table)
|
90
|
-
super(table)
|
91
|
-
|
92
|
-
table_exists? &&
|
93
|
-
inherits_from_temporal? &&
|
94
|
-
has_consistency_constraint? &&
|
95
|
-
has_history_indexes?
|
96
|
-
end
|
97
|
-
|
98
|
-
def description
|
99
|
-
'be in history schema'
|
100
|
-
end
|
101
|
-
|
102
|
-
def failure_message
|
103
|
-
"expected #{table} ".tap do |message|
|
104
|
-
message << [
|
105
|
-
("to exist in the #{history_schema} schema" unless @existance),
|
106
|
-
("to inherit from #{temporal_schema}.#{table}" unless @inheritance),
|
107
|
-
("to have a timeline consistency constraint" unless @constraint),
|
108
|
-
("to have history indexes" unless @indexes)
|
109
|
-
].compact.to_sentence
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def failure_message_when_negated
|
114
|
-
"expected #{table} ".tap do |message|
|
115
|
-
message << [
|
116
|
-
("to not exist in the #{history_schema} schema" if @existance),
|
117
|
-
("to not inherit from #{temporal_schema}.#{table}" if @inheritance),
|
118
|
-
("to not have a timeline consistency constraint" if @constraint),
|
119
|
-
("to not have history indexes" if @indexes)
|
120
|
-
].compact.to_sentence
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
private
|
125
|
-
def table_exists?
|
126
|
-
@existance = relation_exists? :in => history_schema
|
127
|
-
end
|
128
|
-
|
129
|
-
def inherits_from_temporal?
|
130
|
-
binds = ["#{history_schema}.#{table}", "#{temporal_schema}.#{table}"]
|
131
|
-
|
132
|
-
@inheritance = select_value(<<-SQL, binds, 'Check inheritance') == true
|
133
|
-
SELECT EXISTS (
|
134
|
-
SELECT 1 FROM pg_catalog.pg_inherits
|
135
|
-
WHERE inhrelid = ?::regclass::oid
|
136
|
-
AND inhparent = ?::regclass::oid
|
137
|
-
)
|
138
|
-
SQL
|
139
|
-
end
|
140
|
-
|
141
|
-
def has_history_indexes?
|
142
|
-
binds = [ history_schema, table ]
|
143
|
-
|
144
|
-
indexes = select_values(<<-SQL, binds, 'Check history indexes')
|
145
|
-
SELECT indexdef FROM pg_indexes
|
146
|
-
WHERE schemaname = ?
|
147
|
-
AND tablename = ?
|
148
|
-
SQL
|
149
|
-
|
150
|
-
fqtn = [history_schema, table].join('.')
|
151
|
-
|
152
|
-
expected = [
|
153
|
-
"CREATE INDEX index_#{table}_temporal_on_lower_validity ON #{fqtn} USING btree (lower(validity))",
|
154
|
-
"CREATE INDEX index_#{table}_temporal_on_upper_validity ON #{fqtn} USING btree (upper(validity))",
|
155
|
-
"CREATE INDEX index_#{table}_temporal_on_validity ON #{fqtn} USING gist (validity)",
|
156
|
-
|
157
|
-
"CREATE INDEX #{table}_inherit_pkey ON #{fqtn} USING btree (id)",
|
158
|
-
"CREATE INDEX #{table}_instance_history ON #{fqtn} USING btree (id, recorded_at)",
|
159
|
-
"CREATE UNIQUE INDEX #{table}_pkey ON #{fqtn} USING btree (hid)",
|
160
|
-
"CREATE INDEX #{table}_recorded_at ON #{fqtn} USING btree (recorded_at)",
|
161
|
-
"CREATE INDEX #{table}_timeline_consistency ON #{fqtn} USING gist (id, validity)"
|
162
|
-
]
|
163
|
-
|
164
|
-
@indexes = (expected - indexes).empty?
|
165
|
-
end
|
166
|
-
|
167
|
-
def has_consistency_constraint?
|
168
|
-
binds = {
|
169
|
-
conname: connection.timeline_consistency_constraint_name(table),
|
170
|
-
connamespace: history_schema,
|
171
|
-
conrelid: [history_schema, table].join('.'),
|
172
|
-
attname: connection.primary_key(table)
|
173
|
-
}
|
174
|
-
|
175
|
-
@constraint = select_value(<<-SQL, binds, 'Check Consistency Constraint') == true
|
176
|
-
SELECT EXISTS (
|
177
|
-
SELECT 1 FROM pg_catalog.pg_constraint
|
178
|
-
WHERE conname = :conname
|
179
|
-
AND contype = 'x'
|
180
|
-
AND conrelid = :conrelid::regclass
|
181
|
-
AND connamespace = (
|
182
|
-
SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = :connamespace
|
183
|
-
)
|
184
|
-
AND conkey = (
|
185
|
-
SELECT array_agg(attnum) FROM pg_catalog.pg_attribute
|
186
|
-
WHERE attname IN (:attname, 'validity')
|
187
|
-
AND attrelid = :conrelid::regclass
|
188
|
-
)
|
189
|
-
)
|
190
|
-
SQL
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
def have_history_backing
|
195
|
-
HaveHistoryBacking.new
|
196
|
-
end
|
197
|
-
|
198
|
-
|
199
|
-
# ##################################################################
|
200
|
-
# Checks that a table exists in the Public schema, is an updatable
|
201
|
-
# view and has an INSERT, UPDATE and DELETE triggers.
|
202
|
-
#
|
203
|
-
class HavePublicInterface < Base
|
204
|
-
def matches?(table)
|
205
|
-
super(table)
|
206
|
-
|
207
|
-
view_exists? && [ is_updatable?, has_triggers? ].all?
|
208
|
-
end
|
209
|
-
|
210
|
-
def description
|
211
|
-
'be an updatable view'
|
212
|
-
end
|
213
|
-
|
214
|
-
def failure_message
|
215
|
-
"expected #{table} ".tap do |message|
|
216
|
-
message << [
|
217
|
-
("to exist in the #{public_schema} schema" unless @existance ),
|
218
|
-
('to be an updatable view' unless @updatable ),
|
219
|
-
('to have an INSERT trigger' unless @insert_trigger),
|
220
|
-
('to have an UPDATE trigger' unless @update_trigger),
|
221
|
-
('to have a DELETE trigger' unless @delete_trigger)
|
222
|
-
].compact.to_sentence
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
def failure_message_when_negated
|
227
|
-
"expected #{table} ".tap do |message|
|
228
|
-
message << [
|
229
|
-
("to not exist in the #{public_schema} schema" if @existance ),
|
230
|
-
('to not be an updatable view' if @updatable ),
|
231
|
-
('to not have an INSERT trigger' if @insert_trigger),
|
232
|
-
('to not have an UPDATE trigger' if @update_trigger),
|
233
|
-
('to not have a DELETE trigger' if @delete_trigger)
|
234
|
-
].compact.to_sentence
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
private
|
239
|
-
def view_exists?
|
240
|
-
@existance = relation_exists? :in => public_schema, :kind => :view
|
241
|
-
end
|
242
|
-
|
243
|
-
def is_updatable?
|
244
|
-
binds = [ public_schema, table ]
|
245
|
-
|
246
|
-
@updatable = select_value(<<-SQL, binds, 'Check updatable') == 'YES'
|
247
|
-
SELECT is_updatable FROM information_schema.views
|
248
|
-
WHERE table_schema = ? AND table_name = ?
|
249
|
-
SQL
|
250
|
-
end
|
251
|
-
|
252
|
-
def has_triggers?
|
253
|
-
triggers = select_values(<<-SQL, [ public_schema, table ], 'Check triggers')
|
254
|
-
SELECT t.tgname
|
255
|
-
FROM pg_catalog.pg_trigger t, pg_catalog.pg_class c, pg_catalog.pg_namespace n
|
256
|
-
WHERE t.tgrelid = c.relfilenode
|
257
|
-
AND n.oid = c.relnamespace
|
258
|
-
AND n.nspname = ?
|
259
|
-
AND c.relname = ?;
|
260
|
-
SQL
|
261
|
-
|
262
|
-
@insert_trigger = triggers.include? 'chronomodel_insert'
|
263
|
-
@update_trigger = triggers.include? 'chronomodel_update'
|
264
|
-
@delete_trigger = triggers.include? 'chronomodel_delete'
|
265
|
-
|
266
|
-
@insert_trigger && @update_trigger && @delete_trigger
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
def have_public_interface
|
271
|
-
HavePublicInterface.new
|
272
|
-
end
|
273
|
-
end
|
274
|
-
|
275
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
module ChronoTest::TimeMachine
|
2
|
-
|
3
|
-
# This module contains helpers used throughout the
|
4
|
-
# +ChronoModel::TimeMachine+ specs.
|
5
|
-
#
|
6
|
-
module Helpers
|
7
|
-
def self.included(base)
|
8
|
-
base.extend(self)
|
9
|
-
end
|
10
|
-
|
11
|
-
def adapter
|
12
|
-
ChronoTest.connection
|
13
|
-
end
|
14
|
-
|
15
|
-
def with_revert
|
16
|
-
adapter.transaction do
|
17
|
-
adapter.create_savepoint 'revert'
|
18
|
-
|
19
|
-
yield
|
20
|
-
|
21
|
-
adapter.exec_rollback_to_savepoint 'revert'
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
# If a context object is given, evaluates the given
|
26
|
-
# block in its instance context, then defines a `ts`
|
27
|
-
# on it, backed by an Array, and adds the current
|
28
|
-
# database timestamp to it.
|
29
|
-
#
|
30
|
-
# If a context object is not given, the block is
|
31
|
-
# evaluated in the current context and the above
|
32
|
-
# mangling is done on the blocks' return value.
|
33
|
-
#
|
34
|
-
def ts_eval(ctx = nil, &block)
|
35
|
-
ret = (ctx || self).instance_eval(&block)
|
36
|
-
(ctx || ret).tap do |obj|
|
37
|
-
obj.singleton_class.instance_eval do
|
38
|
-
define_method(:ts) { @_ts ||= [] }
|
39
|
-
end unless obj.methods.include?(:ts)
|
40
|
-
|
41
|
-
now = ChronoTest.connection.select_value('select now()::timestamp') + 'Z'
|
42
|
-
obj.ts.push(Time.parse(now))
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
end
|