2017-07-09 13:41:28 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2014-09-09 05:21:23 -04:00
|
|
|
require "cases/helper"
|
|
|
|
require "models/book"
|
2015-09-13 07:40:25 -04:00
|
|
|
require "support/schema_dumping_helper"
|
2014-09-09 05:21:23 -04:00
|
|
|
|
2014-09-11 07:14:37 -04:00
|
|
|
module ViewBehavior
|
2015-09-13 07:40:25 -04:00
|
|
|
include SchemaDumpingHelper
|
2014-09-11 07:14:37 -04:00
|
|
|
extend ActiveSupport::Concern
|
|
|
|
|
|
|
|
included do
|
|
|
|
fixtures :books
|
|
|
|
end
|
2014-09-09 05:21:23 -04:00
|
|
|
|
|
|
|
class Ebook < ActiveRecord::Base
|
2016-10-13 15:50:38 -04:00
|
|
|
self.table_name = "ebooks'"
|
2014-09-09 05:21:23 -04:00
|
|
|
self.primary_key = "id"
|
|
|
|
end
|
|
|
|
|
2014-09-11 07:14:37 -04:00
|
|
|
def setup
|
|
|
|
super
|
2014-09-09 05:21:23 -04:00
|
|
|
@connection = ActiveRecord::Base.connection
|
Use squiggly heredoc to strip odd indentation in the executed SQL
Before:
```
LOG: execute <unnamed>: SELECT t.oid, t.typname
FROM pg_type as t
WHERE t.typname IN ('int2', 'int4', 'int8', 'oid', 'float4', 'float8', 'bool')
LOG: execute <unnamed>: SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
FROM pg_type as t
LEFT JOIN pg_range as r ON oid = rngtypid
WHERE
t.typname IN ('int2', 'int4', 'int8', 'oid', 'float4', 'float8', 'text', 'varchar', 'char', 'name', 'bpchar', 'bool', 'bit', 'varbit', 'timestamptz', 'date', 'money', 'bytea', 'point', 'hstore', 'json', 'jsonb', 'cidr', 'inet', 'uuid', 'xml', 'tsvector', 'macaddr', 'citext', 'ltree', 'interval', 'path', 'line', 'polygon', 'circle', 'lseg', 'box', 'time', 'timestamp', 'numeric')
OR t.typtype IN ('r', 'e', 'd')
OR t.typinput::varchar = 'array_in'
OR t.typelem != 0
LOG: statement: SHOW TIME ZONE
LOG: statement: SELECT 1
LOG: execute <unnamed>: SELECT COUNT(*)
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
AND c.relname = 'accounts'
AND n.nspname = ANY (current_schemas(false))
```
After:
```
LOG: execute <unnamed>: SELECT t.oid, t.typname
FROM pg_type as t
WHERE t.typname IN ('int2', 'int4', 'int8', 'oid', 'float4', 'float8', 'bool')
LOG: execute <unnamed>: SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
FROM pg_type as t
LEFT JOIN pg_range as r ON oid = rngtypid
WHERE
t.typname IN ('int2', 'int4', 'int8', 'oid', 'float4', 'float8', 'text', 'varchar', 'char', 'name', 'bpchar', 'bool', 'bit', 'varbit', 'timestamptz', 'date', 'money', 'bytea', 'point', 'hstore', 'json', 'jsonb', 'cidr', 'inet', 'uuid', 'xml', 'tsvector', 'macaddr', 'citext', 'ltree', 'interval', 'path', 'line', 'polygon', 'circle', 'lseg', 'box', 'time', 'timestamp', 'numeric')
OR t.typtype IN ('r', 'e', 'd')
OR t.typinput::varchar = 'array_in'
OR t.typelem != 0
LOG: statement: SHOW TIME ZONE
LOG: statement: SELECT 1
LOG: execute <unnamed>: SELECT COUNT(*)
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
AND c.relname = 'accounts'
AND n.nspname = ANY (current_schemas(false))
```
2018-11-05 08:47:26 -05:00
|
|
|
create_view "ebooks'", <<~SQL
|
2014-09-11 07:14:37 -04:00
|
|
|
SELECT id, name, status FROM books WHERE format = 'ebook'
|
2014-09-09 05:21:23 -04:00
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
2014-09-11 07:14:37 -04:00
|
|
|
def teardown
|
|
|
|
super
|
2016-10-13 15:50:38 -04:00
|
|
|
drop_view "ebooks'"
|
2014-09-09 05:21:23 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_reading
|
|
|
|
books = Ebook.all
|
|
|
|
assert_equal [books(:rfr).id], books.map(&:id)
|
|
|
|
assert_equal ["Ruby for Rails"], books.map(&:name)
|
|
|
|
end
|
|
|
|
|
2015-09-13 07:03:30 -04:00
|
|
|
def test_views
|
|
|
|
assert_equal [Ebook.table_name], @connection.views
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_view_exists
|
|
|
|
view_name = Ebook.table_name
|
|
|
|
assert @connection.view_exists?(view_name), "'#{view_name}' view should exist"
|
|
|
|
end
|
|
|
|
|
2014-09-09 05:21:23 -04:00
|
|
|
def test_table_exists
|
|
|
|
view_name = Ebook.table_name
|
2016-12-29 03:33:15 -05:00
|
|
|
assert_not @connection.table_exists?(view_name), "'#{view_name}' table should not exist"
|
2014-09-09 05:21:23 -04:00
|
|
|
end
|
|
|
|
|
2015-09-22 09:58:18 -04:00
|
|
|
def test_views_ara_valid_data_sources
|
|
|
|
view_name = Ebook.table_name
|
|
|
|
assert @connection.data_source_exists?(view_name), "'#{view_name}' should be a data source"
|
|
|
|
end
|
|
|
|
|
2014-09-09 05:21:23 -04:00
|
|
|
def test_column_definitions
|
|
|
|
assert_equal([["id", :integer],
|
|
|
|
["name", :string],
|
|
|
|
["status", :integer]], Ebook.columns.map { |c| [c.name, c.type] })
|
|
|
|
end
|
2014-09-09 06:00:01 -04:00
|
|
|
|
|
|
|
def test_attributes
|
2016-08-16 03:30:11 -04:00
|
|
|
assert_equal({ "id" => 2, "name" => "Ruby for Rails", "status" => 0 },
|
2014-09-09 06:00:01 -04:00
|
|
|
Ebook.first.attributes)
|
|
|
|
end
|
2014-09-09 09:17:46 -04:00
|
|
|
|
|
|
|
def test_does_not_assume_id_column_as_primary_key
|
|
|
|
model = Class.new(ActiveRecord::Base) do
|
2016-10-13 15:50:38 -04:00
|
|
|
self.table_name = "ebooks'"
|
2014-09-09 09:17:46 -04:00
|
|
|
end
|
|
|
|
assert_nil model.primary_key
|
|
|
|
end
|
2015-09-13 07:40:25 -04:00
|
|
|
|
|
|
|
def test_does_not_dump_view_as_table
|
2016-10-13 15:50:38 -04:00
|
|
|
schema = dump_table_schema "ebooks'"
|
|
|
|
assert_no_match %r{create_table "ebooks'"}, schema
|
2015-09-13 07:40:25 -04:00
|
|
|
end
|
2016-10-13 15:50:38 -04:00
|
|
|
|
|
|
|
private
|
|
|
|
def quote_table_name(name)
|
|
|
|
@connection.quote_table_name(name)
|
|
|
|
end
|
2014-09-09 06:00:01 -04:00
|
|
|
end
|
|
|
|
|
2014-09-11 07:14:37 -04:00
|
|
|
if ActiveRecord::Base.connection.supports_views?
|
2016-08-06 13:55:02 -04:00
|
|
|
class ViewWithPrimaryKeyTest < ActiveRecord::TestCase
|
|
|
|
include ViewBehavior
|
2014-09-11 07:14:37 -04:00
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
private
|
|
|
|
def create_view(name, query)
|
2016-10-13 15:50:38 -04:00
|
|
|
@connection.execute "CREATE VIEW #{quote_table_name(name)} AS #{query}"
|
2016-08-06 13:55:02 -04:00
|
|
|
end
|
2014-09-11 07:14:37 -04:00
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
def drop_view(name)
|
2016-10-13 15:50:38 -04:00
|
|
|
@connection.execute "DROP VIEW #{quote_table_name(name)}" if @connection.view_exists? name
|
2016-08-06 13:55:02 -04:00
|
|
|
end
|
2014-09-11 07:14:37 -04:00
|
|
|
end
|
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
|
|
|
|
include SchemaDumpingHelper
|
|
|
|
fixtures :books
|
2014-09-09 06:00:01 -04:00
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
class Paperback < ActiveRecord::Base; end
|
2014-09-09 06:00:01 -04:00
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
setup do
|
|
|
|
@connection = ActiveRecord::Base.connection
|
Use squiggly heredoc to strip odd indentation in the executed SQL
Before:
```
LOG: execute <unnamed>: SELECT t.oid, t.typname
FROM pg_type as t
WHERE t.typname IN ('int2', 'int4', 'int8', 'oid', 'float4', 'float8', 'bool')
LOG: execute <unnamed>: SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
FROM pg_type as t
LEFT JOIN pg_range as r ON oid = rngtypid
WHERE
t.typname IN ('int2', 'int4', 'int8', 'oid', 'float4', 'float8', 'text', 'varchar', 'char', 'name', 'bpchar', 'bool', 'bit', 'varbit', 'timestamptz', 'date', 'money', 'bytea', 'point', 'hstore', 'json', 'jsonb', 'cidr', 'inet', 'uuid', 'xml', 'tsvector', 'macaddr', 'citext', 'ltree', 'interval', 'path', 'line', 'polygon', 'circle', 'lseg', 'box', 'time', 'timestamp', 'numeric')
OR t.typtype IN ('r', 'e', 'd')
OR t.typinput::varchar = 'array_in'
OR t.typelem != 0
LOG: statement: SHOW TIME ZONE
LOG: statement: SELECT 1
LOG: execute <unnamed>: SELECT COUNT(*)
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
AND c.relname = 'accounts'
AND n.nspname = ANY (current_schemas(false))
```
After:
```
LOG: execute <unnamed>: SELECT t.oid, t.typname
FROM pg_type as t
WHERE t.typname IN ('int2', 'int4', 'int8', 'oid', 'float4', 'float8', 'bool')
LOG: execute <unnamed>: SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
FROM pg_type as t
LEFT JOIN pg_range as r ON oid = rngtypid
WHERE
t.typname IN ('int2', 'int4', 'int8', 'oid', 'float4', 'float8', 'text', 'varchar', 'char', 'name', 'bpchar', 'bool', 'bit', 'varbit', 'timestamptz', 'date', 'money', 'bytea', 'point', 'hstore', 'json', 'jsonb', 'cidr', 'inet', 'uuid', 'xml', 'tsvector', 'macaddr', 'citext', 'ltree', 'interval', 'path', 'line', 'polygon', 'circle', 'lseg', 'box', 'time', 'timestamp', 'numeric')
OR t.typtype IN ('r', 'e', 'd')
OR t.typinput::varchar = 'array_in'
OR t.typelem != 0
LOG: statement: SHOW TIME ZONE
LOG: statement: SELECT 1
LOG: execute <unnamed>: SELECT COUNT(*)
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
AND c.relname = 'accounts'
AND n.nspname = ANY (current_schemas(false))
```
2018-11-05 08:47:26 -05:00
|
|
|
@connection.execute <<~SQL
|
2016-08-22 21:24:08 -04:00
|
|
|
CREATE VIEW paperbacks
|
|
|
|
AS SELECT name, status FROM books WHERE format = 'paperback'
|
|
|
|
SQL
|
2016-08-06 13:55:02 -04:00
|
|
|
end
|
2014-09-09 06:00:01 -04:00
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
teardown do
|
|
|
|
@connection.execute "DROP VIEW paperbacks" if @connection.view_exists? "paperbacks"
|
|
|
|
end
|
2014-09-09 06:00:01 -04:00
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
def test_reading
|
|
|
|
books = Paperback.all
|
|
|
|
assert_equal ["Agile Web Development with Rails"], books.map(&:name)
|
|
|
|
end
|
2014-09-09 06:00:01 -04:00
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
def test_views
|
|
|
|
assert_equal [Paperback.table_name], @connection.views
|
|
|
|
end
|
2015-09-13 07:03:30 -04:00
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
def test_view_exists
|
|
|
|
view_name = Paperback.table_name
|
|
|
|
assert @connection.view_exists?(view_name), "'#{view_name}' view should exist"
|
|
|
|
end
|
2015-09-13 07:03:30 -04:00
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
def test_table_exists
|
|
|
|
view_name = Paperback.table_name
|
2016-12-29 03:33:15 -05:00
|
|
|
assert_not @connection.table_exists?(view_name), "'#{view_name}' table should not exist"
|
2016-08-06 13:55:02 -04:00
|
|
|
end
|
2014-09-09 06:00:01 -04:00
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
def test_column_definitions
|
|
|
|
assert_equal([["name", :string],
|
|
|
|
["status", :integer]], Paperback.columns.map { |c| [c.name, c.type] })
|
|
|
|
end
|
2014-09-09 06:00:01 -04:00
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
def test_attributes
|
2016-08-16 03:30:11 -04:00
|
|
|
assert_equal({ "name" => "Agile Web Development with Rails", "status" => 2 },
|
2016-08-06 13:55:02 -04:00
|
|
|
Paperback.first.attributes)
|
|
|
|
end
|
2014-09-09 09:17:46 -04:00
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
def test_does_not_have_a_primary_key
|
|
|
|
assert_nil Paperback.primary_key
|
|
|
|
end
|
2015-09-13 07:40:25 -04:00
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
def test_does_not_dump_view_as_table
|
|
|
|
schema = dump_table_schema "paperbacks"
|
|
|
|
assert_no_match %r{create_table "paperbacks"}, schema
|
|
|
|
end
|
2015-09-13 07:40:25 -04:00
|
|
|
end
|
2015-09-03 22:41:31 -04:00
|
|
|
|
2016-09-14 04:57:52 -04:00
|
|
|
# sqlite dose not support CREATE, INSERT, and DELETE for VIEW
|
2018-09-23 00:45:19 -04:00
|
|
|
if current_adapter?(:Mysql2Adapter, :SQLServerAdapter, :PostgreSQLAdapter)
|
2017-04-12 09:09:15 -04:00
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
class UpdateableViewTest < ActiveRecord::TestCase
|
|
|
|
self.use_transactional_tests = false
|
|
|
|
fixtures :books
|
|
|
|
|
|
|
|
class PrintedBook < ActiveRecord::Base
|
|
|
|
self.primary_key = "id"
|
|
|
|
end
|
|
|
|
|
|
|
|
setup do
|
|
|
|
@connection = ActiveRecord::Base.connection
|
Use squiggly heredoc to strip odd indentation in the executed SQL
Before:
```
LOG: execute <unnamed>: SELECT t.oid, t.typname
FROM pg_type as t
WHERE t.typname IN ('int2', 'int4', 'int8', 'oid', 'float4', 'float8', 'bool')
LOG: execute <unnamed>: SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
FROM pg_type as t
LEFT JOIN pg_range as r ON oid = rngtypid
WHERE
t.typname IN ('int2', 'int4', 'int8', 'oid', 'float4', 'float8', 'text', 'varchar', 'char', 'name', 'bpchar', 'bool', 'bit', 'varbit', 'timestamptz', 'date', 'money', 'bytea', 'point', 'hstore', 'json', 'jsonb', 'cidr', 'inet', 'uuid', 'xml', 'tsvector', 'macaddr', 'citext', 'ltree', 'interval', 'path', 'line', 'polygon', 'circle', 'lseg', 'box', 'time', 'timestamp', 'numeric')
OR t.typtype IN ('r', 'e', 'd')
OR t.typinput::varchar = 'array_in'
OR t.typelem != 0
LOG: statement: SHOW TIME ZONE
LOG: statement: SELECT 1
LOG: execute <unnamed>: SELECT COUNT(*)
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
AND c.relname = 'accounts'
AND n.nspname = ANY (current_schemas(false))
```
After:
```
LOG: execute <unnamed>: SELECT t.oid, t.typname
FROM pg_type as t
WHERE t.typname IN ('int2', 'int4', 'int8', 'oid', 'float4', 'float8', 'bool')
LOG: execute <unnamed>: SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
FROM pg_type as t
LEFT JOIN pg_range as r ON oid = rngtypid
WHERE
t.typname IN ('int2', 'int4', 'int8', 'oid', 'float4', 'float8', 'text', 'varchar', 'char', 'name', 'bpchar', 'bool', 'bit', 'varbit', 'timestamptz', 'date', 'money', 'bytea', 'point', 'hstore', 'json', 'jsonb', 'cidr', 'inet', 'uuid', 'xml', 'tsvector', 'macaddr', 'citext', 'ltree', 'interval', 'path', 'line', 'polygon', 'circle', 'lseg', 'box', 'time', 'timestamp', 'numeric')
OR t.typtype IN ('r', 'e', 'd')
OR t.typinput::varchar = 'array_in'
OR t.typelem != 0
LOG: statement: SHOW TIME ZONE
LOG: statement: SELECT 1
LOG: execute <unnamed>: SELECT COUNT(*)
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
AND c.relname = 'accounts'
AND n.nspname = ANY (current_schemas(false))
```
2018-11-05 08:47:26 -05:00
|
|
|
@connection.execute <<~SQL
|
2016-08-22 21:24:08 -04:00
|
|
|
CREATE VIEW printed_books
|
|
|
|
AS SELECT id, name, status, format FROM books WHERE format = 'paperback'
|
|
|
|
SQL
|
2016-08-06 13:55:02 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
teardown do
|
|
|
|
@connection.execute "DROP VIEW printed_books" if @connection.view_exists? "printed_books"
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_update_record
|
|
|
|
book = PrintedBook.first
|
|
|
|
book.name = "AWDwR"
|
|
|
|
book.save!
|
|
|
|
book.reload
|
|
|
|
assert_equal "AWDwR", book.name
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_insert_record
|
|
|
|
PrintedBook.create! name: "Rails in Action", status: 0, format: "paperback"
|
|
|
|
|
|
|
|
new_book = PrintedBook.last
|
|
|
|
assert_equal "Rails in Action", new_book.name
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_update_record_to_fail_view_conditions
|
|
|
|
book = PrintedBook.first
|
|
|
|
book.format = "ebook"
|
|
|
|
book.save!
|
|
|
|
|
|
|
|
assert_raises ActiveRecord::RecordNotFound do
|
|
|
|
book.reload
|
|
|
|
end
|
|
|
|
end
|
2015-09-03 22:41:31 -04:00
|
|
|
end
|
2017-01-15 21:33:47 -05:00
|
|
|
end # end of `if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)`
|
2016-12-29 03:33:15 -05:00
|
|
|
end # end of `if ActiveRecord::Base.connection.supports_views?`
|
2015-09-03 22:41:31 -04:00
|
|
|
|
2018-09-23 00:45:19 -04:00
|
|
|
if ActiveRecord::Base.connection.supports_materialized_views?
|
2016-08-06 13:55:02 -04:00
|
|
|
class MaterializedViewTest < ActiveRecord::PostgreSQLTestCase
|
|
|
|
include ViewBehavior
|
2015-09-03 22:41:31 -04:00
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
private
|
|
|
|
def create_view(name, query)
|
2016-10-13 15:50:38 -04:00
|
|
|
@connection.execute "CREATE MATERIALIZED VIEW #{quote_table_name(name)} AS #{query}"
|
2016-08-06 13:55:02 -04:00
|
|
|
end
|
2015-09-03 22:41:31 -04:00
|
|
|
|
2016-08-06 13:55:02 -04:00
|
|
|
def drop_view(name)
|
2016-10-13 15:50:38 -04:00
|
|
|
@connection.execute "DROP MATERIALIZED VIEW #{quote_table_name(name)}" if @connection.view_exists? name
|
2016-08-06 13:55:02 -04:00
|
|
|
end
|
2015-09-03 22:41:31 -04:00
|
|
|
end
|
|
|
|
end
|