piggly 1.2.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +163 -0
- data/Rakefile +29 -15
- data/bin/piggly +4 -244
- data/lib/piggly.rb +19 -17
- data/lib/piggly/command.rb +9 -0
- data/lib/piggly/command/base.rb +148 -0
- data/lib/piggly/command/report.rb +162 -0
- data/lib/piggly/command/test.rb +157 -0
- data/lib/piggly/command/trace.rb +90 -0
- data/lib/piggly/command/untrace.rb +78 -0
- data/lib/piggly/compiler.rb +7 -5
- data/lib/piggly/compiler/cache_dir.rb +119 -0
- data/lib/piggly/compiler/coverage_report.rb +63 -0
- data/lib/piggly/compiler/trace_compiler.rb +105 -0
- data/lib/piggly/config.rb +47 -22
- data/lib/piggly/dumper.rb +9 -0
- data/lib/piggly/dumper/index.rb +121 -0
- data/lib/piggly/dumper/qualified_name.rb +36 -0
- data/lib/piggly/dumper/qualified_type.rb +81 -0
- data/lib/piggly/dumper/reified_procedure.rb +142 -0
- data/lib/piggly/dumper/skeleton_procedure.rb +102 -0
- data/lib/piggly/installer.rb +84 -42
- data/lib/piggly/parser.rb +43 -49
- data/lib/piggly/parser/grammar.tt +289 -313
- data/lib/piggly/parser/nodes.rb +270 -211
- data/lib/piggly/parser/traversal.rb +35 -33
- data/lib/piggly/parser/treetop_ruby19_patch.rb +1 -1
- data/lib/piggly/profile.rb +81 -60
- data/lib/piggly/reporter.rb +5 -18
- data/lib/piggly/reporter/base.rb +103 -0
- data/lib/piggly/reporter/html_dsl.rb +63 -0
- data/lib/piggly/reporter/index.rb +108 -0
- data/lib/piggly/reporter/procedure.rb +104 -0
- data/lib/piggly/reporter/resources/highlight.js +21 -0
- data/lib/piggly/reporter/{piggly.css → resources/piggly.css} +52 -12
- data/lib/piggly/reporter/{sortable.js → resources/sortable.js} +0 -0
- data/lib/piggly/tags.rb +280 -0
- data/lib/piggly/task.rb +191 -40
- data/lib/piggly/util.rb +8 -27
- data/lib/piggly/util/blankslate.rb +114 -0
- data/lib/piggly/util/cacheable.rb +19 -0
- data/lib/piggly/util/enumerable.rb +44 -0
- data/lib/piggly/util/file.rb +17 -0
- data/lib/piggly/util/process_queue.rb +96 -0
- data/lib/piggly/util/thunk.rb +39 -0
- data/lib/piggly/version.rb +8 -8
- data/spec/examples/compiler/cacheable_spec.rb +190 -0
- data/spec/examples/compiler/report_spec.rb +25 -0
- data/spec/{compiler → examples/compiler}/trace_spec.rb +7 -57
- data/spec/examples/config_spec.rb +61 -0
- data/spec/examples/dumper/index_spec.rb +197 -0
- data/spec/examples/dumper/procedure_spec.rb +116 -0
- data/spec/{grammar → examples/grammar}/expression_spec.rb +60 -60
- data/spec/{grammar → examples/grammar}/statements/assignment_spec.rb +15 -15
- data/spec/examples/grammar/statements/declaration_spec.rb +21 -0
- data/spec/{grammar → examples/grammar}/statements/exception_spec.rb +10 -10
- data/spec/{grammar → examples/grammar}/statements/if_spec.rb +47 -34
- data/spec/{grammar → examples/grammar}/statements/loop_spec.rb +5 -5
- data/spec/{grammar → examples/grammar}/statements/sql_spec.rb +11 -11
- data/spec/{grammar → examples/grammar}/tokens/comment_spec.rb +11 -11
- data/spec/{grammar → examples/grammar}/tokens/datatype_spec.rb +14 -8
- data/spec/{grammar → examples/grammar}/tokens/identifier_spec.rb +26 -10
- data/spec/{grammar → examples/grammar}/tokens/keyword_spec.rb +5 -5
- data/spec/{grammar → examples/grammar}/tokens/label_spec.rb +7 -7
- data/spec/{grammar → examples/grammar}/tokens/literal_spec.rb +1 -1
- data/spec/examples/grammar/tokens/lval_spec.rb +50 -0
- data/spec/{grammar → examples/grammar}/tokens/number_spec.rb +1 -1
- data/spec/{grammar → examples/grammar}/tokens/sqlkeywords_spec.rb +1 -1
- data/spec/{grammar → examples/grammar}/tokens/string_spec.rb +9 -9
- data/spec/{grammar → examples/grammar}/tokens/whitespace_spec.rb +1 -1
- data/spec/examples/installer_spec.rb +59 -0
- data/spec/examples/parser/nodes_spec.rb +73 -0
- data/spec/examples/parser/traversal_spec.rb +14 -0
- data/spec/examples/parser_spec.rb +115 -0
- data/spec/examples/profile_spec.rb +153 -0
- data/spec/{reporter/html_spec.rb → examples/reporter/html/dsl_spec.rb} +0 -0
- data/spec/examples/reporter/html/index_spec.rb +0 -0
- data/spec/examples/reporter/html_spec.rb +1 -0
- data/spec/examples/reporter_spec.rb +0 -0
- data/spec/{compiler → examples}/tags_spec.rb +10 -10
- data/spec/examples/task_spec.rb +0 -0
- data/spec/examples/util/cacheable_spec.rb +41 -0
- data/spec/examples/util/enumerable_spec.rb +64 -0
- data/spec/examples/util/file_spec.rb +40 -0
- data/spec/examples/util/process_queue_spec.rb +16 -0
- data/spec/examples/util/thunk_spec.rb +58 -0
- data/spec/examples/version_spec.rb +0 -0
- data/spec/issues/007_spec.rb +25 -0
- data/spec/issues/008_spec.rb +73 -0
- data/spec/issues/018_spec.rb +25 -0
- data/spec/spec_helper.rb +253 -9
- metadata +136 -93
- data/README.markdown +0 -116
- data/lib/piggly/compiler/cache.rb +0 -151
- data/lib/piggly/compiler/pretty.rb +0 -67
- data/lib/piggly/compiler/queue.rb +0 -46
- data/lib/piggly/compiler/tags.rb +0 -244
- data/lib/piggly/compiler/trace.rb +0 -91
- data/lib/piggly/filecache.rb +0 -40
- data/lib/piggly/parser/parser.rb +0 -11794
- data/lib/piggly/reporter/html.rb +0 -207
- data/spec/compiler/cache_spec.rb +0 -9
- data/spec/compiler/pretty_spec.rb +0 -9
- data/spec/compiler/queue_spec.rb +0 -3
- data/spec/compiler/rewrite_spec.rb +0 -3
- data/spec/config_spec.rb +0 -58
- data/spec/filecache_spec.rb +0 -70
- data/spec/fixtures/snippets.sql +0 -158
- data/spec/grammar/tokens/lval_spec.rb +0 -50
- data/spec/parser_spec.rb +0 -8
- data/spec/profile_spec.rb +0 -5
data/lib/piggly/config.rb
CHANGED
@@ -1,34 +1,19 @@
|
|
1
1
|
module Piggly
|
2
2
|
class Config
|
3
|
+
end
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
self.class.send(:define_method, "#{name}=") do |value|
|
10
|
-
instance_variable_set("@#{name}", value)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
config_accessor :cache_root => File.expand_path(File.join(Dir.pwd, 'piggly', 'cache')),
|
16
|
-
:report_root => File.expand_path(File.join(Dir.pwd, 'piggly', 'reports')),
|
17
|
-
:piggly_root => PIGGLY_ROOT,
|
18
|
-
:trace_prefix => 'PIGGLY',
|
19
|
-
:aggregate => false
|
20
|
-
|
21
|
-
def self.path(root, file=nil)
|
22
|
-
if file
|
5
|
+
class << Config
|
6
|
+
def path(root, file=nil)
|
7
|
+
if file.nil?
|
8
|
+
root
|
9
|
+
else
|
23
10
|
file[%r{^\.\.|^\/|^(?:[A-Z]:)?/}i] ?
|
24
11
|
file : # ../path, /path, or D:\path that isn't relative to root
|
25
12
|
File.join(root, file)
|
26
|
-
else
|
27
|
-
root
|
28
13
|
end
|
29
14
|
end
|
30
15
|
|
31
|
-
def
|
16
|
+
def mkpath(root, file=nil)
|
32
17
|
if file.nil?
|
33
18
|
FileUtils.makedirs(root)
|
34
19
|
root
|
@@ -39,5 +24,45 @@ module Piggly
|
|
39
24
|
end
|
40
25
|
end
|
41
26
|
|
27
|
+
private
|
28
|
+
|
29
|
+
def config_accessor(hash)
|
30
|
+
hash = hash.clone
|
31
|
+
hash.keys.each do |name|
|
32
|
+
define_method(name) do
|
33
|
+
instance_variable_get("@#{name}") || hash[name]
|
34
|
+
end
|
35
|
+
|
36
|
+
define_method("#{name}?") do
|
37
|
+
instance_variable_get("@#{name}") || hash[name]
|
38
|
+
end
|
39
|
+
|
40
|
+
define_method("#{name}=") do |value|
|
41
|
+
instance_variable_set("@#{name}", value)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Config
|
48
|
+
config_accessor \
|
49
|
+
:cache_root => File.expand_path("#{Dir.pwd}/piggly/cache"),
|
50
|
+
:report_root => File.expand_path("#{Dir.pwd}/piggly/reports"),
|
51
|
+
:database_yml => nil,
|
52
|
+
:connection_name => "piggly",
|
53
|
+
:trace_prefix => "PIGGLY",
|
54
|
+
:accumulate => false,
|
55
|
+
:dry_run => false,
|
56
|
+
:filters => []
|
57
|
+
|
58
|
+
alias accumulate? accumulate
|
59
|
+
|
60
|
+
def path(*args)
|
61
|
+
self.class.path(*args)
|
62
|
+
end
|
63
|
+
|
64
|
+
def mkpath(*args)
|
65
|
+
self.class.mkpath(*args)
|
66
|
+
end
|
42
67
|
end
|
43
68
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Piggly
|
2
|
+
module Dumper
|
3
|
+
autoload :Index, "piggly/dumper/index"
|
4
|
+
autoload :QualifiedName, "piggly/dumper/qualified_name"
|
5
|
+
autoload :QualifiedType, "piggly/dumper/qualified_type"
|
6
|
+
autoload :ReifiedProcedure, "piggly/dumper/reified_procedure"
|
7
|
+
autoload :SkeletonProcedure, "piggly/dumper/skeleton_procedure"
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module Piggly
|
2
|
+
module Dumper
|
3
|
+
|
4
|
+
#
|
5
|
+
# The index file stores metadata about every procedure, but the source
|
6
|
+
# code is stored in a separate file for each procedure.
|
7
|
+
#
|
8
|
+
class Index
|
9
|
+
|
10
|
+
def initialize(config)
|
11
|
+
@config = config
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [String]
|
15
|
+
def path
|
16
|
+
@config.mkpath("#{@config.cache_root}/Dumper", "index.yml")
|
17
|
+
end
|
18
|
+
|
19
|
+
# Updates the index with the given list of Procedure values
|
20
|
+
# @return [void]
|
21
|
+
def update(procedures)
|
22
|
+
newest = Util::Enumerable.index_by(procedures){|x| x.identifier }
|
23
|
+
|
24
|
+
removed = index.values.reject{|p| newest.include?(p.identifier) }
|
25
|
+
removed.each{|p| p.purge_source(@config) }
|
26
|
+
|
27
|
+
added = procedures.reject{|p| index.include?(p.identifier) }
|
28
|
+
added.each{|p| p.store_source(@config) }
|
29
|
+
|
30
|
+
changed = procedures.select do |p|
|
31
|
+
if mine = index[p.identifier]
|
32
|
+
# If both are skeletons, they will have the same source because they
|
33
|
+
# are read from the same file, so don't bother checking that case
|
34
|
+
not (mine.skeleton? and p.skeleton?) and
|
35
|
+
mine.source(@config) != p.source(@config)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
changed.each{|p| p.store_source(@config) }
|
39
|
+
|
40
|
+
@index = newest
|
41
|
+
store_index
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns a list of Procedure values from the index
|
45
|
+
def procedures
|
46
|
+
index.values
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns the Procedure with the given identifier
|
50
|
+
def [](identifier)
|
51
|
+
p = index[identifier]
|
52
|
+
p.dup if p
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the shortest human-readable label that distinctly identifies
|
56
|
+
# the given procedure from the other procedures in the index
|
57
|
+
def label(procedure)
|
58
|
+
others =
|
59
|
+
procedures.reject{|p| p.oid == procedure.oid }
|
60
|
+
|
61
|
+
same =
|
62
|
+
others.all?{|p| p.name.schema == procedure.name.schema }
|
63
|
+
|
64
|
+
name =
|
65
|
+
if same
|
66
|
+
procedure.name.name
|
67
|
+
else
|
68
|
+
procedure.name.to_s
|
69
|
+
end
|
70
|
+
|
71
|
+
samenames =
|
72
|
+
others.select{|p| p.name == procedure.name }
|
73
|
+
|
74
|
+
if samenames.empty?
|
75
|
+
# Name is unique enough
|
76
|
+
name.to_s
|
77
|
+
else
|
78
|
+
sameargs =
|
79
|
+
samenames.select{|p| p.arg_types == procedure.arg_types }
|
80
|
+
|
81
|
+
if sameargs.empty?
|
82
|
+
# Name and arg types are unique enough
|
83
|
+
"#{name}(#{procedure.arg_types.join(", ")})"
|
84
|
+
else
|
85
|
+
samemodes =
|
86
|
+
sameargs.select{|p| p.arg_modes == procedure.arg_modes }
|
87
|
+
|
88
|
+
if samemodes.empty?
|
89
|
+
# Name, arg types, and arg modes are unique enough
|
90
|
+
"#{name}(#{procedure.arg_modes.zip(procedure.arg_types).map{|a,b| "#{a} #{b}" }.join(", ")})"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def index
|
99
|
+
@index ||= load_index
|
100
|
+
end
|
101
|
+
|
102
|
+
# Load the index from disk
|
103
|
+
def load_index
|
104
|
+
contents =
|
105
|
+
unless File.exists?(path)
|
106
|
+
[]
|
107
|
+
else
|
108
|
+
YAML.load(File.read(path))
|
109
|
+
end
|
110
|
+
|
111
|
+
Util::Enumerable.index_by(contents){|x| x.identifier }
|
112
|
+
end
|
113
|
+
|
114
|
+
# Write the index to disk
|
115
|
+
def store_index
|
116
|
+
File.open(path, "wb"){|io| YAML.dump(procedures.map{|p| p.skeleton }, io) }
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Piggly
|
2
|
+
module Dumper
|
3
|
+
|
4
|
+
class QualifiedName
|
5
|
+
attr_reader :schema, :name
|
6
|
+
|
7
|
+
def initialize(schema, name)
|
8
|
+
@schema, @name = schema, name
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [String]
|
12
|
+
def quote
|
13
|
+
if @schema
|
14
|
+
'"' + @schema + '"."' + @name + '"'
|
15
|
+
else
|
16
|
+
'"' + @name + '"'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [String]
|
21
|
+
def to_s
|
22
|
+
if @schema
|
23
|
+
@schema + "." + @name
|
24
|
+
else
|
25
|
+
@name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Boolean]
|
30
|
+
def ==(other)
|
31
|
+
self.to_s == other.to_s
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Piggly
|
2
|
+
module Dumper
|
3
|
+
|
4
|
+
class QualifiedType
|
5
|
+
attr_reader :schema, :name
|
6
|
+
|
7
|
+
def initialize(schema, name)
|
8
|
+
@schema, @name = schema, normalize(name)
|
9
|
+
end
|
10
|
+
|
11
|
+
def shorten
|
12
|
+
self.class.new(nil, @name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def quote
|
16
|
+
if @schema
|
17
|
+
'"' + @schema + '"."' + @name + '"'
|
18
|
+
else
|
19
|
+
'"' + @name + '"'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
if @schema and !%w[public pg_catalog].include?(@schema)
|
25
|
+
@schema + "." + readable(@name)
|
26
|
+
else
|
27
|
+
readable(@name)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==(other)
|
32
|
+
self.to_s == other.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def normalize(name)
|
38
|
+
# select format_type(ret.oid, null), ret.typname
|
39
|
+
# from pg_type as ret
|
40
|
+
# where ret.typname <> format_type(ret.oid, null)
|
41
|
+
# and ret.typname not like '\\_%'
|
42
|
+
# group by ret.typname, format_type(ret.oid, null)
|
43
|
+
# order by format_type(ret.oid, null);
|
44
|
+
case name
|
45
|
+
when /(.*)\[\]/ then "_#{normalize($1)}"
|
46
|
+
when '"any"' then "any"
|
47
|
+
when "bigint" then "int8"
|
48
|
+
when "bit varying" then "varbit"
|
49
|
+
when "boolean" then "bool"
|
50
|
+
when '"char"' then "char"
|
51
|
+
when "character" then "bpchar"
|
52
|
+
when "character varying" then "varchar"
|
53
|
+
when "double precision" then "float8"
|
54
|
+
when "information_schema\.(.*)" then $1
|
55
|
+
when "integer" then "int4"
|
56
|
+
when "real" then "float4"
|
57
|
+
when "smallint" then "int2"
|
58
|
+
when "timestamp without time zone" then "timestamp"
|
59
|
+
when "timestamp with time zone" then "timestamptz"
|
60
|
+
when "time without time zone" then "time"
|
61
|
+
when "time with time zone" then "timetz"
|
62
|
+
else name
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def readable(name)
|
67
|
+
case name
|
68
|
+
when /^_(.*)/ then "#{readable($1)}[]"
|
69
|
+
when "bpchar" then "char"
|
70
|
+
when /^float4(.*)/ then "real#{$1}"
|
71
|
+
when /^int2(.*)/ then "smallint#{$1}"
|
72
|
+
when /^int4(.*)/ then "int#{$1}"
|
73
|
+
when /^int8(.*)/ then "bigint#{$1}"
|
74
|
+
when /^serial4(.*)/ then "serial#{$1}"
|
75
|
+
else name
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module Piggly
|
2
|
+
module Dumper
|
3
|
+
|
4
|
+
#
|
5
|
+
# Differs from SkeletonProcedure in that the procedure source code is stored
|
6
|
+
# as an instance variable.
|
7
|
+
#
|
8
|
+
class ReifiedProcedure < SkeletonProcedure
|
9
|
+
|
10
|
+
def initialize(source, *args)
|
11
|
+
super(*args)
|
12
|
+
@source = source.strip
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [String]
|
16
|
+
def source(config)
|
17
|
+
@source
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [void]
|
21
|
+
def store_source(config)
|
22
|
+
if @source.include?("$PIGGLY$")
|
23
|
+
raise "Procedure `#{@name}' is already instrumented. " +
|
24
|
+
"This means the original source wasn't restored after the " +
|
25
|
+
"last coverage run. You must restore the source manually."
|
26
|
+
end
|
27
|
+
|
28
|
+
File.open(source_path(config), "wb"){|io| io.write(@source) }
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [SkeletonProcedure]
|
32
|
+
def skeleton
|
33
|
+
SkeletonProcedure.new(@oid, @name, @strict, @secdef, @setof, @type,
|
34
|
+
@volatility, @arg_modes, @arg_names, @arg_types,
|
35
|
+
@arg_defaults)
|
36
|
+
end
|
37
|
+
|
38
|
+
def skeleton?
|
39
|
+
false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class << ReifiedProcedure
|
44
|
+
# Rewrite "i", "o", and "b", otherwise pass-through
|
45
|
+
MODES = Hash.new{|h,k| k }.update \
|
46
|
+
"i" => "in",
|
47
|
+
"o" => "out",
|
48
|
+
"b" => "inout",
|
49
|
+
"v" => "variadic"
|
50
|
+
|
51
|
+
# Rewrite "i", "v", and "s", otherwise pass-through
|
52
|
+
VOLATILITY = Hash.new{|h,k| k }.update \
|
53
|
+
"i" => "immutable",
|
54
|
+
"v" => "volatile",
|
55
|
+
"s" => "stable"
|
56
|
+
|
57
|
+
def mode(mode)
|
58
|
+
MODES[mode]
|
59
|
+
end
|
60
|
+
|
61
|
+
def volatility(mode)
|
62
|
+
VOLATILITY[mode]
|
63
|
+
end
|
64
|
+
|
65
|
+
def defaults(exprs, count, total)
|
66
|
+
exprs = exprs.split(", ")
|
67
|
+
nreqd = total - count
|
68
|
+
|
69
|
+
if nreqd > 0 and exprs.length == count
|
70
|
+
Array.new(nreqd) + exprs
|
71
|
+
else
|
72
|
+
raise "Couldn't parse default arguments"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns a list of all PL/pgSQL stored procedures in the current database
|
77
|
+
#
|
78
|
+
# @return [Array<ReifiedProcedure>]
|
79
|
+
def all(connection)
|
80
|
+
connection.query(<<-SQL).map{|x| from_hash(x) }
|
81
|
+
select
|
82
|
+
pro.oid,
|
83
|
+
nschema.nspname as nschema,
|
84
|
+
pro.proname as name,
|
85
|
+
pro.proisstrict as strict,
|
86
|
+
pro.prosecdef as secdef,
|
87
|
+
pro.provolatile as volatility,
|
88
|
+
pro.proretset as setof,
|
89
|
+
rschema.nspname as tschema,
|
90
|
+
ret.typname as type,
|
91
|
+
pro.prosrc as source,
|
92
|
+
pro.pronargs as arg_count,
|
93
|
+
array_to_string(pro.proargmodes, ',') as arg_modes,
|
94
|
+
array_to_string(pro.proargnames, ',') as arg_names,
|
95
|
+
case when proallargtypes is not null then
|
96
|
+
-- use proalltypes array if its non-null
|
97
|
+
array_to_string(array(select format_type(proallargtypes[k], null)
|
98
|
+
from generate_series(array_lower(proallargtypes, 1),
|
99
|
+
array_upper(proallargtypes, 1)) as k), ',')
|
100
|
+
else
|
101
|
+
-- fallback to oidvector proargtypes
|
102
|
+
oidvectortypes(pro.proargtypes)
|
103
|
+
end as arg_types,
|
104
|
+
pro.pronargdefaults as arg_defaults_count,
|
105
|
+
coalesce(pg_get_expr(pro.proargdefaults, 0), '') as arg_defaults
|
106
|
+
from pg_proc as pro,
|
107
|
+
pg_type as ret,
|
108
|
+
pg_namespace as nschema,
|
109
|
+
pg_namespace as rschema
|
110
|
+
where pro.pronamespace = nschema.oid
|
111
|
+
and ret.typnamespace = rschema.oid
|
112
|
+
and pro.proname not like 'piggly_%'
|
113
|
+
and pro.prorettype = ret.oid
|
114
|
+
and pro.prolang = (select oid from pg_language where lanname = 'plpgsql')
|
115
|
+
and pro.pronamespace not in (select oid
|
116
|
+
from pg_namespace
|
117
|
+
where nspname like 'pg_%'
|
118
|
+
or nspname like 'information_schema')
|
119
|
+
SQL
|
120
|
+
end
|
121
|
+
|
122
|
+
# Construct a ReifiedProcedure from a result row (Hash)
|
123
|
+
#
|
124
|
+
# @return [ReifiedProcedure]
|
125
|
+
def from_hash(hash)
|
126
|
+
new(hash["source"],
|
127
|
+
hash["oid"],
|
128
|
+
QualifiedName.new(hash["nschema"], hash["name"]),
|
129
|
+
hash["strict"] == "t",
|
130
|
+
hash["secdef"] == "t",
|
131
|
+
hash["setof"] == "t",
|
132
|
+
QualifiedType.new(hash["tschema"], hash["type"]),
|
133
|
+
volatility(hash["volatility"]),
|
134
|
+
hash["arg_modes"].to_s.split(",").map{|x| mode(x.strip) },
|
135
|
+
hash["arg_names"].to_s.split(",").map{|x| QualifiedName.new(nil, x.strip) },
|
136
|
+
hash["arg_types"].to_s.split(",").map{|x| QualifiedType.new(nil, x.strip) },
|
137
|
+
defaults(hash["arg_defaults"], hash["arg_defaults_count"].to_i, hash["arg_count"].to_i))
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|