rails-pg-procs 1.0.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.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README +15 -0
- data/README.rdoc +18 -0
- data/Rakefile +60 -0
- data/VERSION +1 -0
- data/docs/classes/ActiveRecord.html +117 -0
- data/docs/classes/ActiveRecord/ConnectionAdapters.html +113 -0
- data/docs/classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html +589 -0
- data/docs/classes/ActiveRecord/ConnectionAdapters/ProcedureDefinition.html +110 -0
- data/docs/classes/ActiveRecord/ConnectionAdapters/TriggerDefinition.html +519 -0
- data/docs/classes/ActiveRecord/ConnectionAdapters/TypeDefinition.html +110 -0
- data/docs/classes/ActiveRecord/SchemaDumper.html +371 -0
- data/docs/classes/Inflector.html +164 -0
- data/docs/classes/SchemaProcs.html +211 -0
- data/docs/classes/SqlFormat.html +139 -0
- data/docs/classes/String.html +117 -0
- data/docs/classes/Symbol.html +117 -0
- data/docs/created.rid +1 -0
- data/docs/fr_class_index.html +36 -0
- data/docs/fr_method_index.html +63 -0
- data/docs/index.html +22 -0
- data/docs/rdoc-style.css +208 -0
- data/init.rb +3 -0
- data/install.rb +1 -0
- data/lib/connection_adapters/aggregagtes_definition.rb +1 -0
- data/lib/connection_adapters/connection_adapters.rb +8 -0
- data/lib/connection_adapters/index_definition.rb +24 -0
- data/lib/connection_adapters/postgresql_adapter.rb +256 -0
- data/lib/connection_adapters/procedure_definition.rb +6 -0
- data/lib/connection_adapters/schema_definition.rb +24 -0
- data/lib/connection_adapters/schema_statements.rb +11 -0
- data/lib/connection_adapters/trigger_definition.rb +114 -0
- data/lib/connection_adapters/type_definition.rb +17 -0
- data/lib/connection_adapters/view_definition.rb +34 -0
- data/lib/inflector.rb +10 -0
- data/lib/rails_pg_procs.rb +8 -0
- data/lib/schema_dumper.rb +96 -0
- data/lib/schema_procs.rb +19 -0
- data/lib/sql_format.rb +17 -0
- data/tasks/rails_pg_procs.rake +4 -0
- data/test/connection.rb +16 -0
- data/test/procedure_test.rb +78 -0
- data/test/rails_pg_procs_test.rb +246 -0
- data/test/test_helper.rb +41 -0
- data/test/trigger_test.rb +87 -0
- data/test/type_test.rb +46 -0
- data/test/view_test.rb +36 -0
- data/uninstall.rb +1 -0
- metadata +112 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
class TypeDefinition < Struct.new(:id, :name, :columns)
|
4
|
+
|
5
|
+
def to_sql(action="create", options={})
|
6
|
+
case action
|
7
|
+
when "create", :create
|
8
|
+
"CREATE SCHEMA #{name.to_sql_name} AUTHORIZATION #{owner.to_sql_name}"
|
9
|
+
# TODO - [ schema_element ]
|
10
|
+
when "drop", :drop
|
11
|
+
"DROP SCHEMA #{name.to_sql_name} #{cascade_or_restrict(options[:cascade])}"
|
12
|
+
# TODO - [ IF EXISTS ]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
class ViewDefinition
|
4
|
+
include SchemaProcs
|
5
|
+
attr_accessor :id, :name, :columns, :view_body
|
6
|
+
def initialize(id, name, columns=[], &block)
|
7
|
+
@id = id
|
8
|
+
self.name = name
|
9
|
+
self.columns = columns
|
10
|
+
self.view_body = block
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_rdl
|
14
|
+
" create_view(#{ActiveSupport::Inflector.symbolize(name)}) { $#{name}_body$\n #{view_body.call}\n $#{name}_body$ }"
|
15
|
+
end
|
16
|
+
|
17
|
+
# CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW NAME [ ( column_name [, ...] ) ]
|
18
|
+
# AS query
|
19
|
+
# [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]
|
20
|
+
# DROP VIEW [ IF EXISTS ] NAME [, ...] [ CASCADE | RESTRICT ]
|
21
|
+
def to_sql(action="create", options={})
|
22
|
+
case action
|
23
|
+
when "create", :create
|
24
|
+
ret = "CREATE OR REPLACE#{' TEMPORARY' if options[:temp] } VIEW #{name.to_sql_name}
|
25
|
+
AS #{view_body.call}"
|
26
|
+
# TODO - [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]
|
27
|
+
when "drop", :drop
|
28
|
+
ret = "DROP VIEW #{name.to_sql_name} #{cascade_or_restrict(options[:cascade])}"
|
29
|
+
end
|
30
|
+
ret
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/inflector.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
module ActiveSupport::Inflector
|
2
|
+
def triggerize(table_name, events=[], before=false)
|
3
|
+
events.join(" or ").gsub(":", "").tr(" ", "_").downcase + "_" + (before ? "before_" : "after_") + table_name.to_s + "_trigger"
|
4
|
+
end
|
5
|
+
|
6
|
+
def symbolize(val)
|
7
|
+
return "'#{val}'" if val =~ /-/
|
8
|
+
":#{val}"
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
# This class is used to dump the database schema for some connection to some
|
3
|
+
# output format (i.e., ActiveRecord::Schema).
|
4
|
+
class SchemaDumper
|
5
|
+
include SchemaProcs
|
6
|
+
|
7
|
+
# TODO -Implement checks on SchemaDumper instance
|
8
|
+
# to ensure we do this only when using pg db.
|
9
|
+
def postgres?
|
10
|
+
adapter_name == 'PostgreSQL'
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def get_type(types)
|
15
|
+
case types
|
16
|
+
when Array
|
17
|
+
types.collect {|type|
|
18
|
+
get_type(type)
|
19
|
+
}.join(", ")
|
20
|
+
when String && /^\d+$/
|
21
|
+
type = @connection.select_value("SELECT typname FROM pg_type WHERE oid = '#{types}'")
|
22
|
+
return type = 'nil' if type == 'void'
|
23
|
+
get_type(type)
|
24
|
+
when String
|
25
|
+
return %("#{types}") if types =~ /[\s\(]/
|
26
|
+
ActiveSupport::Inflector.symbolize(types)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# TODO - Facilitate create_proc(name, [argname, argtype] and create_proc(name, [argmode, argname, argtype] ...
|
31
|
+
def procedures(stream, conditions=nil)
|
32
|
+
@connection.procedures(conditions).each { |proc_row|
|
33
|
+
oid, name, namespace, owner, lang, is_agg, sec_def, is_strict, ret_set, volatile, nargs, ret_type, arg_types, arg_names, src, bin, acl = proc_row
|
34
|
+
is_agg = is_agg == 't'
|
35
|
+
is_strict = is_strict == 't'
|
36
|
+
ret_set = ret_set == 't'
|
37
|
+
arg_names ||= ''
|
38
|
+
args = get_type(arg_types.split(" "))#.zip(arg_names.split(" "))
|
39
|
+
|
40
|
+
stream.print " create_proc(#{name.to_sql_name}, [#{args}], :return => #{get_type(ret_type)}"
|
41
|
+
stream.print ", :resource => ['#{bin}', '#{src}']" unless bin == '-'
|
42
|
+
stream.print ", :set => true" if ret_set
|
43
|
+
stream.print ", :strict => true" if is_strict
|
44
|
+
stream.print ", :behavior => '#{behavior(volatile)}'" unless volatile == 'v'
|
45
|
+
stream.print ", :lang => '#{lang}')"
|
46
|
+
stream.print " {\n <<-#{ActiveSupport::Inflector.underscore(name)}_sql\n#{src.chomp}\n #{ActiveSupport::Inflector.underscore(name)}_sql\n }" if bin == '-'
|
47
|
+
stream.print "\n"
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def schemas(stream)
|
52
|
+
schemas = @connection.schemas
|
53
|
+
schemas.each {|schema|
|
54
|
+
stream.puts schema.to_rdl
|
55
|
+
}
|
56
|
+
stream.puts unless schemas.empty?
|
57
|
+
end
|
58
|
+
|
59
|
+
def triggers(table_name, stream)
|
60
|
+
triggers = @connection.triggers(table_name)
|
61
|
+
triggers.each {|trigger|
|
62
|
+
stream.puts trigger.to_rdl
|
63
|
+
}
|
64
|
+
stream.puts unless triggers.empty?
|
65
|
+
end
|
66
|
+
|
67
|
+
def types(stream)
|
68
|
+
@connection.types.each {|type|
|
69
|
+
stream.print " create_type #{type.name.to_sql_name}, "
|
70
|
+
stream.puts "#{ type.columns.collect{|column, type| "[#{ActiveSupport::Inflector.symbolize(column)}, #{get_type(type)}]"}.join(", ") }"
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
alias_method :procless_tables, :tables
|
75
|
+
def tables(stream)
|
76
|
+
schemas(stream)
|
77
|
+
types(stream)
|
78
|
+
procedures(stream, "!= 'sql'")
|
79
|
+
procless_tables(stream)
|
80
|
+
procedures(stream, "= 'sql'")
|
81
|
+
end
|
82
|
+
|
83
|
+
alias_method :schemaless_table, :table
|
84
|
+
def table table, stream
|
85
|
+
schemaless_table table, stream
|
86
|
+
end
|
87
|
+
|
88
|
+
alias_method :indexes_before_triggers, :indexes
|
89
|
+
def indexes(table, stream)
|
90
|
+
# schema, table = table.split '.'
|
91
|
+
indexes_before_triggers(table, stream)
|
92
|
+
triggers(table, stream)
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
data/lib/schema_procs.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module SchemaProcs
|
2
|
+
@@_cascade_or_restrict = Proc.new {|which| which ? 'CASCADE' : 'RESTRICT' }
|
3
|
+
@@_strict_or_null = Proc.new {|strict| strict ? 'STRICT' : 'CALLED ON NULL INPUT' }
|
4
|
+
@@_definer_or_invoker = Proc.new {|definer| definer ? 'DEFINER' : 'INVOKER' }
|
5
|
+
@@_behavior = Proc.new {|volatile| %w{immutable stable volatile}.grep(/^#{volatile[0,1]}.+/).to_s }
|
6
|
+
|
7
|
+
def cascade_or_restrict(cascade=false)
|
8
|
+
@@_cascade_or_restrict.call(cascade)
|
9
|
+
end
|
10
|
+
def strict_or_null(is_strict=false)
|
11
|
+
@@_strict_or_null.call(is_strict)
|
12
|
+
end
|
13
|
+
def definer_or_invoker(definer=false)
|
14
|
+
@@_definer_or_invoker.call(definer)
|
15
|
+
end
|
16
|
+
def behavior(volatile='v')
|
17
|
+
@@_behavior.call(volatile)
|
18
|
+
end
|
19
|
+
end
|
data/lib/sql_format.rb
ADDED
data/test/connection.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
print "Using native PostgreSQL\n"
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
ActiveRecord::Base.logger = Logger.new("debug.log")
|
5
|
+
|
6
|
+
ActiveRecord::Base.configurations = {
|
7
|
+
'rails_pg_procs' => {
|
8
|
+
:adapter => 'postgresql',
|
9
|
+
:username => 'postgres',
|
10
|
+
:database => 'activerecord_unittest',
|
11
|
+
:min_messages => 'warning',
|
12
|
+
:allow_concurrency => false
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
ActiveRecord::Base.establish_connection('rails_pg_procs') unless ActiveRecord::Base.connected?
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ProcedureTest < Test::Unit::TestCase
|
4
|
+
def test_create_proc
|
5
|
+
[
|
6
|
+
/logf()/,
|
7
|
+
/\'resource\/file\',\s\'method\'/,
|
8
|
+
/LANGUAGE C/
|
9
|
+
].each {|re|
|
10
|
+
assert_match(re, @connection.send("get_proc_query", "logf", [], :return => nil, :resource => ['resource/file', 'method'], :lang => "C"))
|
11
|
+
}
|
12
|
+
|
13
|
+
[
|
14
|
+
/logf()/,
|
15
|
+
/\'resource\/file\',\s\'logf\'/,
|
16
|
+
/LANGUAGE C/
|
17
|
+
].each {|re|
|
18
|
+
assert_match(re, @connection.send("get_proc_query", "logf", [], :return => nil, :resource => ['resource/file'], :lang => "C"))
|
19
|
+
}
|
20
|
+
|
21
|
+
[
|
22
|
+
/CREATE OR REPLACE FUNCTION "logf"()/,
|
23
|
+
/RETURNS VOID/,
|
24
|
+
/\$logf_body\$/,
|
25
|
+
Regexp.new(@query_body),
|
26
|
+
/LANGUAGE plpgsql/,
|
27
|
+
/IMMUTABLE/,
|
28
|
+
/STRICT/,
|
29
|
+
/EXTERNAL SECURITY DEFINER/
|
30
|
+
].each {|re|
|
31
|
+
assert_match(re, @connection.send("get_proc_query", "logf", [], :return => nil, :definer => true, :strict => true, :behavior => 'immutable') { @query_body })
|
32
|
+
}
|
33
|
+
|
34
|
+
[
|
35
|
+
/update_trade_materials_statuses_logf()/,
|
36
|
+
/RETURNS VOID/,
|
37
|
+
/\$update_trade_materials_statuses_logf_body\$/,
|
38
|
+
Regexp.new(@query_body),
|
39
|
+
/LANGUAGE SQL/,
|
40
|
+
].each {|re|
|
41
|
+
assert_match(re, @connection.send("get_proc_query", "update_trade_materials_statuses_logf", [], :return => nil, :lang => :SQL) { @query_body })
|
42
|
+
}
|
43
|
+
|
44
|
+
[
|
45
|
+
/update_trade_materials_statuses_logf()/,
|
46
|
+
/RETURNS trigger/,
|
47
|
+
/\$update_trade_materials_statuses_logf_body\$/,
|
48
|
+
Regexp.new(@query_body),
|
49
|
+
/LANGUAGE plpgsql/,
|
50
|
+
/VOLATILE/,
|
51
|
+
/CALLED ON NULL INPUT/,
|
52
|
+
/EXTERNAL SECURITY INVOKER/
|
53
|
+
].each {|re|
|
54
|
+
assert_match(re, @connection.send("get_proc_query", "update_trade_materials_statuses_logf", [], :return => "trigger") { @query_body })
|
55
|
+
}
|
56
|
+
|
57
|
+
assert_exception(ActiveRecord::StatementInvalid, "Missing block or library file.") {
|
58
|
+
@connection.create_proc("update_trade_materials_statuses_logf", [], :return => "trigger")
|
59
|
+
}
|
60
|
+
|
61
|
+
assert_nothing_raised {
|
62
|
+
@connection.create_proc(:update_trade_materials_statuses_logf, [], :return => :trigger) {
|
63
|
+
@query_body
|
64
|
+
}
|
65
|
+
}
|
66
|
+
count = @connection.select_value("select count(*) from pg_proc where proname = 'update_trade_materials_statuses_logf'", "count")
|
67
|
+
assert_equal("1", count)
|
68
|
+
assert_nothing_raised {
|
69
|
+
@connection.drop_proc("update_trade_materials_statuses_logf", [])
|
70
|
+
}
|
71
|
+
count = @connection.select_value("select count(*) from pg_proc where proname = 'update_trade_materials_statuses_logf'", "count")
|
72
|
+
assert_equal("0", count)
|
73
|
+
|
74
|
+
assert_equal('CASCADE', @connection.send("cascade_or_restrict", true))
|
75
|
+
assert_equal('RESTRICT', @connection.send("cascade_or_restrict"))
|
76
|
+
assert_equal('RESTRICT', @connection.send("cascade_or_restrict", false))
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
|
4
|
+
class RailsPgProcsTest < Test::Unit::TestCase
|
5
|
+
# include ActiveRecord::ConnectionAdapters::SchemaStatements
|
6
|
+
|
7
|
+
def test_complicated_schema_dumper
|
8
|
+
with_proc(:update_trade_materials_statuses_logf, [], :return => :trigger) {
|
9
|
+
with_trigger(:test_table, [:insert, :update], :before => true, :name => :update_trade_materials_statuses_logt, :function => :update_trade_materials_statuses_logf) {
|
10
|
+
assert_no_exception(NoMethodError) do
|
11
|
+
stream = StringIO.new
|
12
|
+
dumper = ActiveRecord::SchemaDumper.new(@connection)
|
13
|
+
dumper.send(:triggers, :test_table, stream)
|
14
|
+
stream.rewind
|
15
|
+
assert_match /add_trigger "test_table", [:insert, :update], :before => true, :name => :update_trade_materials_statuses_logt, :function => :update_trade_materials_statuses_logf/, stream.read
|
16
|
+
|
17
|
+
stream = StringIO.new
|
18
|
+
dumper.send(:procedures, stream)
|
19
|
+
stream.rewind
|
20
|
+
received_sql = stream.string
|
21
|
+
assert_match " create_proc(\"update_trade_materials_statuses_logf\", [], :return => :trigger, :lang => 'plpgsql') {\n <<-update_trade_materials_statuses_logf_sql\n\n#{@query_body}\n update_trade_materials_statuses_logf_sql\n }".to_regex, received_sql
|
22
|
+
end
|
23
|
+
}
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_functional_dump
|
28
|
+
@connection.create_proc(:f_commacat, [:text, :text], :return => :text) { "BEGIN
|
29
|
+
IF (LENGTH($1) > 0 ) THEN
|
30
|
+
RETURN $1 || ', ' || $2;
|
31
|
+
ELSE
|
32
|
+
RETURN $2;
|
33
|
+
END IF;
|
34
|
+
END;" }
|
35
|
+
begin
|
36
|
+
@connection.execute "DROP AGGREGATE comma(text);"
|
37
|
+
@connection.execute "CREATE AGGREGATE comma(BASETYPE=text, SFUNC=f_commacat, STYPE=text)"
|
38
|
+
rescue ActiveRecord::StatementInvalid
|
39
|
+
end
|
40
|
+
|
41
|
+
@connection.create_schema :somewhere, "postgres"
|
42
|
+
@connection.create_proc('somewhere_else.afunc', []) { "BEGIN\nEND;" }
|
43
|
+
|
44
|
+
@connection.create_schema :somewhere_else
|
45
|
+
|
46
|
+
@connection.create_type(:qualitysmith_user, [:name, :varchar], {:address => "varchar(20)"}, [:zip, "varchar(5)"], [:phone, "numeric(10,0)"])
|
47
|
+
@connection.create_proc("name-with-hyphen", [], :return => :trigger) { " BEGIN\n--Something else goes here\nEND;\n" }
|
48
|
+
@connection.create_proc(:update_trade_materials_statuses_logf, [], :return => :trigger) { " BEGIN\n--Something else goes here\nEND;\n" }
|
49
|
+
@connection.create_proc(:levenshtein, [], :return => :trigger, :resource => ['$libdir/fuzzystrmatch'], :lang => "c")
|
50
|
+
@connection.add_trigger(:test_table, [:insert, :update], :row => true, :name => "update_trade-materials_statuses_logt", :function => :update_trade_materials_statuses_logf)
|
51
|
+
@connection.add_trigger(:test_table, [:insert, :update], :function => :levenshtein)
|
52
|
+
@connection.create_table("a_table_defined_after_the_stored_proc", :force => true) {|t|
|
53
|
+
t.column :name, :varchar
|
54
|
+
}
|
55
|
+
@connection.drop_table "somewhere.table_in_a_specific_schema"
|
56
|
+
@connection.create_table "somewhere.table_in_a_specific_schema" do |t|
|
57
|
+
t.column :name, :text
|
58
|
+
end
|
59
|
+
@connection.create_proc(:sql_proc_with_table_reference, [:integer], :return => :integer, :lang => "SQL") { "SELECT id FROM a_table_defined_after_the_stored_proc WHERE id = $1;" }
|
60
|
+
|
61
|
+
stream = StringIO.new
|
62
|
+
ActiveRecord::SchemaDumper.ignore_tables = []
|
63
|
+
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
|
64
|
+
received_sql = stream.string
|
65
|
+
|
66
|
+
assert_match(%q|create_schema(:somewhere_else, "postgres")|.to_regex, received_sql)
|
67
|
+
assert_match(%q|create_type(:qualitysmith_user, [:name, "character varying"], [:address, "character varying(20)"], [:zip, "character varying(5)"], [:phone, "numeric(10,0)"])|.to_regex, received_sql)
|
68
|
+
assert_match(%q|create_proc('name-with-hyphen'|.to_regex, received_sql)
|
69
|
+
assert_match(%q|create_proc(:update_trade_materials_statuses_logf|.to_regex, received_sql)
|
70
|
+
assert_match(%q|create_proc("levenshtein", []|.to_regex, received_sql)
|
71
|
+
assert_match(%q|add_trigger(:test_table, [:insert, :update], :function => :levenshtein)|.to_regex, received_sql)
|
72
|
+
assert_match(%q|add_trigger(:test_table, [:insert, :update], :row => true, :name => 'update_trade-materials_statuses_logt', :function => :update_trade_materials_statuses_logf)|.to_regex, received_sql)
|
73
|
+
assert_match(%q|create_table "a_table_defined_after_the_stored_proc"|.to_regex, received_sql)
|
74
|
+
assert_match(%q|create_table("somewhere.schema_in_a_specific_table"|.to_regex, received_sql)
|
75
|
+
assert_match(%q|create_proc(:sql_proc_with_table_reference, [:int4], :return => :int4, :lang => 'sql') {\n <<-sql_proc_with_table_reference_sql\n\nSELECT id FROM a_table_defined_after_the_stored_proc WHERE id = $1;\n sql_proc_with_table_reference_sql\n }|.to_regex, received_sql.split("\n")[-9..-2].join("\n"))
|
76
|
+
assert_match(%q|create_proc(:f_commacat, [:text, :text]|.to_regex, received_sql)
|
77
|
+
assert_no_match(%q|lang => 'internal'|.to_regex, received_sql)
|
78
|
+
assert_no_match(%q|create_proc(:comma|.to_regex, received_sql)
|
79
|
+
assert_no_match(%q|create_proc(:afunc|.to_regex, received_sql)
|
80
|
+
|
81
|
+
@connection.drop_proc(:sql_proc_with_table_reference, [:int4])
|
82
|
+
@connection.drop_table("a_table_defined_after_the_stored_proc")
|
83
|
+
@connection.remove_trigger(:test_table, :insert_or_update_after_test_table_trigger)
|
84
|
+
@connection.remove_trigger(:test_table, "update_trade-materials_statuses_logt")
|
85
|
+
@connection.drop_proc(:levenshtein)
|
86
|
+
@connection.drop_proc(:update_trade_materials_statuses_logf)
|
87
|
+
@connection.drop_proc('name-with-hyphen')
|
88
|
+
@connection.drop_type(:qualitysmith_user)
|
89
|
+
begin
|
90
|
+
@connection.execute "DROP AGGREGATE comma(text)"
|
91
|
+
rescue ActiveRecord::StatementInvalid
|
92
|
+
end
|
93
|
+
@connection.drop_proc('somewhere_else.afunc', []) { "BEGIN\nEND;" }
|
94
|
+
@connection.drop_schema "somewhere", :cascade => true
|
95
|
+
@connection.drop_schema "somewhere_else", :cascade => true
|
96
|
+
@connection.drop_proc(:f_commacat, [:text, :text])
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_methods
|
100
|
+
%w(procs types views schemas).each {|meth|
|
101
|
+
%w(create drop).each {|action|
|
102
|
+
assert_respond_to "#{action}_#{meth.singularize}", @connection
|
103
|
+
}
|
104
|
+
assert_respond_to meth, @connection unless meth == 'procs'
|
105
|
+
}
|
106
|
+
%w(procedures triggers add_trigger remove_trigger).each {|meth|
|
107
|
+
assert_respond_to meth, @connection
|
108
|
+
}
|
109
|
+
|
110
|
+
assert !@connection.procedures().nil?, "@connection#procedures returns nil"
|
111
|
+
procedures_count = @connection.procedures.size
|
112
|
+
trigger_count = @connection.triggers(:test_table).size
|
113
|
+
with_proc(:insert_after_test_table_trigger, [], :return => :trigger) {
|
114
|
+
assert_equal 0, trigger_count
|
115
|
+
assert_equal procedures_count + 1, @connection.procedures.size
|
116
|
+
with_trigger(:test_table, [:insert], :row => true) {
|
117
|
+
assert !@connection.triggers(:test_table).nil?, "Triggers for table :test_table returns nil"
|
118
|
+
received = @connection.triggers(:test_table)
|
119
|
+
assert_equal trigger_count + 1, received.size
|
120
|
+
assert_equal "insert_after_test_table_trigger", received.last.name
|
121
|
+
}
|
122
|
+
}
|
123
|
+
assert_equal procedures_count, @connection.procedures.size
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_more_complicated_schema_dumper
|
127
|
+
with_proc(:levenshtein, [:text, :text], :return => nil, :resource => ['$libdir/fuzzystrmatch'], :strict => true, :behavior => 'immutable', :lang => "C") {
|
128
|
+
assert_no_exception(NoMethodError) do
|
129
|
+
dumper = ActiveRecord::SchemaDumper.new(@connection)
|
130
|
+
stream = StringIO.new
|
131
|
+
dumper.send(:procedures, stream)
|
132
|
+
stream.rewind
|
133
|
+
received = stream.read
|
134
|
+
assert_equal " create_proc(\"levenshtein\", [:text, :text], :return => nil, :resource => ['$libdir/fuzzystrmatch', 'levenshtein'], :strict => true, :behavior => 'immutable', :lang => 'c')", received.split("\n")[-1]
|
135
|
+
end
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_schema_definition_class
|
140
|
+
schema = ActiveRecord::ConnectionAdapters::SchemaDefinition.new('rails', 'postgres')
|
141
|
+
assert_match(/CREATE SCHEMA "rails" AUTHORIZATION "postgres"/, schema.to_sql)
|
142
|
+
assert_equal('DROP SCHEMA "rails" RESTRICT', schema.to_sql(:drop))
|
143
|
+
assert_match(/create_schema\ "rails", "postgres"$/, schema.to_rdl)
|
144
|
+
|
145
|
+
count_query = "SELECT count(*) FROM pg_namespace WHERE nspname = 'rails'"
|
146
|
+
assert_nil @connection.schemas.find {|schema| schema.name.to_s == 'rails' }
|
147
|
+
assert_nothing_raised {
|
148
|
+
@connection.create_schema "rails"
|
149
|
+
}
|
150
|
+
# assert_equal 'rails,"$user",public', @connection.schema_search_path
|
151
|
+
assert_equal('"$user",public,rails', @connection.schema_search_path)
|
152
|
+
assert_not_nil @connection.schemas.find {|schema| schema.name.to_s == 'rails' }
|
153
|
+
assert_nothing_raised {
|
154
|
+
@connection.drop_schema "rails"
|
155
|
+
}
|
156
|
+
assert_nil @connection.schemas.find {|schema| schema.name.to_s == 'rails' }
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_schema_dumper_schema
|
160
|
+
@connection.create_schema "rails"
|
161
|
+
assert_no_exception(NoMethodError) do
|
162
|
+
dumper = ActiveRecord::SchemaDumper.new(@connection)
|
163
|
+
stream = StringIO.new
|
164
|
+
dumper.send(:schemas, stream)
|
165
|
+
stream.rewind
|
166
|
+
assert_match /create_schema\ "rails", "postgres"$/, stream.read.chomp
|
167
|
+
end
|
168
|
+
@connection.drop_schema("rails", :cascade => true)
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_schema_dumper_exceptions
|
172
|
+
proc_name, columns = "test_sql_type_proc_with_table_reference", [:integer]
|
173
|
+
assert_equal [], @connection.procedures
|
174
|
+
assert_raise ActiveRecord::StatementInvalid do
|
175
|
+
@connection.create_proc(proc_name, columns, :return => nil, :lang => :sql) {
|
176
|
+
<<-sql
|
177
|
+
SELECT * FROM a_table_that_doesnt_yet_exist WHERE id = '$1';
|
178
|
+
sql
|
179
|
+
}
|
180
|
+
end
|
181
|
+
assert_equal [], @connection.procedures
|
182
|
+
@connection.create_table(:a_table_that_doesnt_yet_exist, :force => true) { |t|
|
183
|
+
t.column :name, :varchar
|
184
|
+
}
|
185
|
+
|
186
|
+
assert_equal [], @connection.procedures
|
187
|
+
assert_nothing_raised do
|
188
|
+
@connection.create_proc(proc_name, columns, :return => :integer, :lang => :sql, :force => true) {
|
189
|
+
<<-sql
|
190
|
+
SELECT id FROM a_table_that_doesnt_yet_exist WHERE id = $1;
|
191
|
+
sql
|
192
|
+
}
|
193
|
+
end
|
194
|
+
@connection.drop_table(:a_table_that_doesnt_yet_exist)
|
195
|
+
@connection.drop_proc(proc_name, columns)
|
196
|
+
assert_equal [], @connection.procedures
|
197
|
+
end
|
198
|
+
|
199
|
+
def test_simple_schema_dumper
|
200
|
+
with_proc(:insert_after_test_table_trigger, [], :return => :trigger) {
|
201
|
+
with_trigger(:test_table, [:insert], :row => true) {
|
202
|
+
assert_no_exception(NoMethodError) do
|
203
|
+
stream = StringIO.new
|
204
|
+
dumper = ActiveRecord::SchemaDumper.new(@connection)
|
205
|
+
dumper.send(:triggers, :test_table, stream)
|
206
|
+
stream.rewind
|
207
|
+
assert_match %q|add_trigger "test_table", [:insert], :row => true|.to_regex, stream.read
|
208
|
+
|
209
|
+
stream = StringIO.new
|
210
|
+
dumper.send(:procedures, stream)
|
211
|
+
stream.rewind
|
212
|
+
assert_match %Q|create_proc(\"insert_after_test_table_trigger\", [], :return => :trigger, :lang => 'plpgsql') {\n <<-insert_after_test_table_trigger_sql\n\n#{@query_body}\n insert_after_test_table_trigger_sql\n }\n|.to_regex, stream.read
|
213
|
+
end
|
214
|
+
}
|
215
|
+
}
|
216
|
+
end
|
217
|
+
|
218
|
+
def test_sym_to_str
|
219
|
+
assert_equal '"abc"', "abc".to_sql_name
|
220
|
+
assert_equal '"abc"', "abc".to_sym.to_sql_name
|
221
|
+
assert_equal "'abc'", "abc".to_sym.to_sql_value
|
222
|
+
end
|
223
|
+
|
224
|
+
private
|
225
|
+
def with_proc(name, columns=[], options={}, &block)
|
226
|
+
# assert_equal [], @connection.procedures
|
227
|
+
|
228
|
+
assert_nil @connection.procedures.find {|procedure| procedure[1] == name }
|
229
|
+
if options[:resource]
|
230
|
+
@connection.create_proc(name, columns, options)
|
231
|
+
else
|
232
|
+
@connection.create_proc(name, columns, options) { @query_body }
|
233
|
+
end
|
234
|
+
assert_equal name.to_s, @connection.procedures.last[1]
|
235
|
+
yield
|
236
|
+
@connection.drop_proc(name, columns)
|
237
|
+
# assert_equal [], @connection.procedures
|
238
|
+
assert_nil @connection.procedures.find {|procedure| procedure[1] == name }
|
239
|
+
end
|
240
|
+
|
241
|
+
def with_trigger(table, events=[], options={}, &block)
|
242
|
+
@connection.add_trigger(table, events, options)
|
243
|
+
yield
|
244
|
+
@connection.remove_trigger(table, options[:name] || ActiveSupport::Inflector.triggerize(table, events, options.has_key?(:before)))
|
245
|
+
end
|
246
|
+
end
|