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.
Files changed (51) hide show
  1. data/.document +5 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE +20 -0
  4. data/README +15 -0
  5. data/README.rdoc +18 -0
  6. data/Rakefile +60 -0
  7. data/VERSION +1 -0
  8. data/docs/classes/ActiveRecord.html +117 -0
  9. data/docs/classes/ActiveRecord/ConnectionAdapters.html +113 -0
  10. data/docs/classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html +589 -0
  11. data/docs/classes/ActiveRecord/ConnectionAdapters/ProcedureDefinition.html +110 -0
  12. data/docs/classes/ActiveRecord/ConnectionAdapters/TriggerDefinition.html +519 -0
  13. data/docs/classes/ActiveRecord/ConnectionAdapters/TypeDefinition.html +110 -0
  14. data/docs/classes/ActiveRecord/SchemaDumper.html +371 -0
  15. data/docs/classes/Inflector.html +164 -0
  16. data/docs/classes/SchemaProcs.html +211 -0
  17. data/docs/classes/SqlFormat.html +139 -0
  18. data/docs/classes/String.html +117 -0
  19. data/docs/classes/Symbol.html +117 -0
  20. data/docs/created.rid +1 -0
  21. data/docs/fr_class_index.html +36 -0
  22. data/docs/fr_method_index.html +63 -0
  23. data/docs/index.html +22 -0
  24. data/docs/rdoc-style.css +208 -0
  25. data/init.rb +3 -0
  26. data/install.rb +1 -0
  27. data/lib/connection_adapters/aggregagtes_definition.rb +1 -0
  28. data/lib/connection_adapters/connection_adapters.rb +8 -0
  29. data/lib/connection_adapters/index_definition.rb +24 -0
  30. data/lib/connection_adapters/postgresql_adapter.rb +256 -0
  31. data/lib/connection_adapters/procedure_definition.rb +6 -0
  32. data/lib/connection_adapters/schema_definition.rb +24 -0
  33. data/lib/connection_adapters/schema_statements.rb +11 -0
  34. data/lib/connection_adapters/trigger_definition.rb +114 -0
  35. data/lib/connection_adapters/type_definition.rb +17 -0
  36. data/lib/connection_adapters/view_definition.rb +34 -0
  37. data/lib/inflector.rb +10 -0
  38. data/lib/rails_pg_procs.rb +8 -0
  39. data/lib/schema_dumper.rb +96 -0
  40. data/lib/schema_procs.rb +19 -0
  41. data/lib/sql_format.rb +17 -0
  42. data/tasks/rails_pg_procs.rake +4 -0
  43. data/test/connection.rb +16 -0
  44. data/test/procedure_test.rb +78 -0
  45. data/test/rails_pg_procs_test.rb +246 -0
  46. data/test/test_helper.rb +41 -0
  47. data/test/trigger_test.rb +87 -0
  48. data/test/type_test.rb +46 -0
  49. data/test/view_test.rb +36 -0
  50. data/uninstall.rb +1 -0
  51. 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
@@ -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,8 @@
1
+ require 'active_record'
2
+ require "active_support/inflector"
3
+
4
+ require 'lib/inflector'
5
+ require 'lib/sql_format'
6
+ require 'lib/schema_procs'
7
+ require 'lib/schema_dumper'
8
+ require 'lib/connection_adapters/connection_adapters'
@@ -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
@@ -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
@@ -0,0 +1,17 @@
1
+ module SqlFormat
2
+ def to_sql_name
3
+ '"' + self.to_s + '"'
4
+ end
5
+
6
+ def to_sql_value
7
+ "'#{self}'"
8
+ end
9
+ end
10
+
11
+ class Symbol
12
+ include SqlFormat
13
+ end
14
+
15
+ class String
16
+ include SqlFormat
17
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :rails_pg_procs do
3
+ # # Task goes here
4
+ # end
@@ -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