piggly-nsd 2.3.3
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.
- checksums.yaml +7 -0
- data/README.md +170 -0
- data/Rakefile +33 -0
- data/bin/piggly +8 -0
- data/lib/piggly/command/base.rb +148 -0
- data/lib/piggly/command/report.rb +162 -0
- data/lib/piggly/command/trace.rb +90 -0
- data/lib/piggly/command/untrace.rb +78 -0
- data/lib/piggly/command.rb +8 -0
- 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 +117 -0
- data/lib/piggly/compiler.rb +7 -0
- data/lib/piggly/config.rb +80 -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 +141 -0
- data/lib/piggly/dumper/reified_procedure.rb +172 -0
- data/lib/piggly/dumper/skeleton_procedure.rb +112 -0
- data/lib/piggly/dumper.rb +9 -0
- data/lib/piggly/installer.rb +137 -0
- data/lib/piggly/parser/grammar.tt +748 -0
- data/lib/piggly/parser/nodes.rb +378 -0
- data/lib/piggly/parser/traversal.rb +50 -0
- data/lib/piggly/parser/treetop_ruby19_patch.rb +21 -0
- data/lib/piggly/parser.rb +69 -0
- data/lib/piggly/profile.rb +108 -0
- data/lib/piggly/reporter/base.rb +106 -0
- data/lib/piggly/reporter/html_dsl.rb +63 -0
- data/lib/piggly/reporter/index.rb +114 -0
- data/lib/piggly/reporter/procedure.rb +129 -0
- data/lib/piggly/reporter/resources/highlight.js +38 -0
- data/lib/piggly/reporter/resources/piggly.css +515 -0
- data/lib/piggly/reporter/resources/sortable.js +493 -0
- data/lib/piggly/reporter.rb +8 -0
- data/lib/piggly/tags.rb +280 -0
- data/lib/piggly/task.rb +215 -0
- 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/util.rb +9 -0
- data/lib/piggly/version.rb +15 -0
- data/lib/piggly.rb +20 -0
- data/spec/examples/compiler/cacheable_spec.rb +190 -0
- data/spec/examples/compiler/report_spec.rb +25 -0
- data/spec/examples/compiler/trace_spec.rb +123 -0
- data/spec/examples/config_spec.rb +63 -0
- data/spec/examples/dumper/index_spec.rb +199 -0
- data/spec/examples/dumper/procedure_spec.rb +116 -0
- data/spec/examples/grammar/expression_spec.rb +302 -0
- data/spec/examples/grammar/statements/assignment_spec.rb +70 -0
- data/spec/examples/grammar/statements/declaration_spec.rb +21 -0
- data/spec/examples/grammar/statements/exception_spec.rb +78 -0
- data/spec/examples/grammar/statements/if_spec.rb +191 -0
- data/spec/examples/grammar/statements/loop_spec.rb +41 -0
- data/spec/examples/grammar/statements/sql_spec.rb +71 -0
- data/spec/examples/grammar/tokens/comment_spec.rb +58 -0
- data/spec/examples/grammar/tokens/datatype_spec.rb +58 -0
- data/spec/examples/grammar/tokens/identifier_spec.rb +74 -0
- data/spec/examples/grammar/tokens/keyword_spec.rb +44 -0
- data/spec/examples/grammar/tokens/label_spec.rb +40 -0
- data/spec/examples/grammar/tokens/literal_spec.rb +30 -0
- data/spec/examples/grammar/tokens/lval_spec.rb +50 -0
- data/spec/examples/grammar/tokens/number_spec.rb +34 -0
- data/spec/examples/grammar/tokens/sqlkeywords_spec.rb +45 -0
- data/spec/examples/grammar/tokens/string_spec.rb +54 -0
- data/spec/examples/grammar/tokens/whitespace_spec.rb +40 -0
- 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 +118 -0
- data/spec/examples/profile_spec.rb +153 -0
- data/spec/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/examples/tags_spec.rb +285 -0
- 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 +59 -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/issues/028_spec.rb +48 -0
- data/spec/issues/032_spec.rb +98 -0
- data/spec/issues/036_spec.rb +41 -0
- data/spec/spec_helper.rb +312 -0
- data/spec/spec_suite.rb +5 -0
- metadata +162 -0
|
@@ -0,0 +1,172 @@
|
|
|
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, oid, name, strict, secdef, setof, type, volatility, arg_modes, arg_names, arg_types, arg_defaults, prokind = "f", language = "plpgsql")
|
|
11
|
+
# Ensure source is UTF-8 encoded
|
|
12
|
+
@source = source.to_s.force_encoding('UTF-8').strip
|
|
13
|
+
|
|
14
|
+
if type.name == "record" and type.schema == "pg_catalog" and arg_modes.include?("t")
|
|
15
|
+
prefix = arg_modes.take_while{|m| m != "t" }.length
|
|
16
|
+
type = RecordType.new(arg_types[prefix..-1], arg_names[prefix..-1], arg_modes[prefix..-1], arg_defaults[prefix..-1])
|
|
17
|
+
arg_modes = arg_modes[0, prefix]
|
|
18
|
+
arg_types = arg_types[0, prefix]
|
|
19
|
+
arg_names = arg_names[0, prefix]
|
|
20
|
+
arg_defaults = arg_defaults[0, prefix]
|
|
21
|
+
setof = false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
super(oid, name, strict, secdef, setof, type, volatility, arg_modes, arg_names, arg_types, arg_defaults, prokind, language)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [String]
|
|
28
|
+
def source(config)
|
|
29
|
+
@source
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [void]
|
|
33
|
+
def store_source(config)
|
|
34
|
+
if @source.include?("$PIGGLY$")
|
|
35
|
+
raise "Procedure `#{@name}' is already instrumented. " +
|
|
36
|
+
"This means the original source wasn't restored after the " +
|
|
37
|
+
"last coverage run. You must restore the source manually."
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
File.open(source_path(config), "wb:UTF-8"){|io| io.write(@source) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @return [SkeletonProcedure]
|
|
44
|
+
def skeleton
|
|
45
|
+
SkeletonProcedure.new(@oid, @name, @strict, @secdef, @setof, @type,
|
|
46
|
+
@volatility, @arg_modes, @arg_names, @arg_types,
|
|
47
|
+
@arg_defaults, @prokind, @language)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def skeleton?
|
|
51
|
+
false
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class << ReifiedProcedure
|
|
56
|
+
# Rewrite "i", "o", and "b", otherwise pass-through
|
|
57
|
+
MODES = Hash.new{|h,k| k }.update \
|
|
58
|
+
"i" => "in",
|
|
59
|
+
"o" => "out",
|
|
60
|
+
"b" => "inout",
|
|
61
|
+
"v" => "variadic"
|
|
62
|
+
|
|
63
|
+
# Rewrite "i", "v", and "s", otherwise pass-through
|
|
64
|
+
VOLATILITY = Hash.new{|h,k| k }.update \
|
|
65
|
+
"i" => "immutable",
|
|
66
|
+
"v" => "volatile",
|
|
67
|
+
"s" => "stable"
|
|
68
|
+
|
|
69
|
+
def mode(mode)
|
|
70
|
+
MODES[mode]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def volatility(mode)
|
|
74
|
+
VOLATILITY[mode]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def defaults(exprs, count, total)
|
|
78
|
+
exprs = if exprs.nil? then [] else exprs.split(", ") end
|
|
79
|
+
|
|
80
|
+
nreqd = total - count
|
|
81
|
+
|
|
82
|
+
if nreqd >= 0 and exprs.length == count
|
|
83
|
+
Array.new(nreqd) + exprs
|
|
84
|
+
else
|
|
85
|
+
raise "Couldn't parse default arguments"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Returns a list of all PL/pgSQL stored procedures in the current database
|
|
90
|
+
#
|
|
91
|
+
# @return [Array<ReifiedProcedure>]
|
|
92
|
+
def all(connection)
|
|
93
|
+
connection.query(<<-SQL).map{|x| from_hash(x) }
|
|
94
|
+
select
|
|
95
|
+
pro.oid,
|
|
96
|
+
nschema.nspname as nschema,
|
|
97
|
+
pro.proname as name,
|
|
98
|
+
pro.proisstrict as strict,
|
|
99
|
+
pro.prosecdef as secdef,
|
|
100
|
+
pro.provolatile as volatility,
|
|
101
|
+
pro.proretset as setof,
|
|
102
|
+
rschema.nspname as tschema,
|
|
103
|
+
ret.typname as type,
|
|
104
|
+
pro.prosrc as source,
|
|
105
|
+
pro.pronargs as arg_count,
|
|
106
|
+
lang.lanname as language,
|
|
107
|
+
array_to_string(pro.proargmodes, ',') as arg_modes,
|
|
108
|
+
array_to_string(pro.proargnames, ',') as arg_names,
|
|
109
|
+
case when proallargtypes is not null then
|
|
110
|
+
-- use proalltypes array if its non-null
|
|
111
|
+
array_to_string(array(select format_type(proallargtypes[k], null)
|
|
112
|
+
from generate_series(array_lower(proallargtypes, 1),
|
|
113
|
+
array_upper(proallargtypes, 1)) as k), ',')
|
|
114
|
+
else
|
|
115
|
+
-- fallback to oidvector proargtypes
|
|
116
|
+
oidvectortypes(pro.proargtypes)
|
|
117
|
+
end as arg_types,
|
|
118
|
+
pro.pronargdefaults as arg_defaults_count,
|
|
119
|
+
coalesce(pg_get_expr(pro.proargdefaults, 0), '') as arg_defaults,
|
|
120
|
+
coalesce(pro.prokind, 'f') as prokind
|
|
121
|
+
from pg_proc as pro,
|
|
122
|
+
pg_type as ret,
|
|
123
|
+
pg_namespace as nschema,
|
|
124
|
+
pg_namespace as rschema,
|
|
125
|
+
pg_language as lang
|
|
126
|
+
where pro.pronamespace = nschema.oid
|
|
127
|
+
and pro.prolang = lang.oid
|
|
128
|
+
and ret.typnamespace = rschema.oid
|
|
129
|
+
and pro.proname not like 'piggly_%'
|
|
130
|
+
and pro.prorettype = ret.oid
|
|
131
|
+
and pro.prolang = (select oid from pg_language where lanname = 'plpgsql')
|
|
132
|
+
and pro.pronamespace not in (select oid
|
|
133
|
+
from pg_namespace
|
|
134
|
+
where nspname like 'pg_%'
|
|
135
|
+
or nspname like 'information_schema')
|
|
136
|
+
SQL
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Construct a ReifiedProcedure from a result row (Hash)
|
|
140
|
+
#
|
|
141
|
+
# @return [ReifiedProcedure]
|
|
142
|
+
def from_hash(hash)
|
|
143
|
+
new(hash["source"],
|
|
144
|
+
hash["oid"],
|
|
145
|
+
QualifiedName.new(hash["nschema"].to_s, hash["name"].to_s),
|
|
146
|
+
hash["strict"] == "t",
|
|
147
|
+
hash["secdef"] == "t",
|
|
148
|
+
hash["setof"] == "t",
|
|
149
|
+
QualifiedType.parse(hash["tschema"].to_s, hash["type"].to_s),
|
|
150
|
+
volatility(hash["volatility"]),
|
|
151
|
+
coalesce(hash["arg_modes"].to_s.split(",").map{|x| mode(x.strip) },
|
|
152
|
+
["in"]*hash["arg_count"].to_i),
|
|
153
|
+
hash["arg_names"].to_s.split(",").map{|x| QualifiedName.new(nil, x.strip) },
|
|
154
|
+
hash["arg_types"].to_s.split(",").map{|x| QualifiedType.parse(x.strip) },
|
|
155
|
+
defaults(hash["arg_defaults"],
|
|
156
|
+
hash["arg_defaults_count"].to_i,
|
|
157
|
+
hash["arg_count"].to_i),
|
|
158
|
+
hash["prokind"].to_s,
|
|
159
|
+
hash["language"].to_s)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def coalesce(value, default)
|
|
163
|
+
if [nil, "", []].include?(value)
|
|
164
|
+
default
|
|
165
|
+
else
|
|
166
|
+
value
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
module Piggly
|
|
2
|
+
module Dumper
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# Encapsulates all the information about a stored procedure, except the
|
|
6
|
+
# procedure's source code, which is assumed to be on disk, loaded as needed.
|
|
7
|
+
#
|
|
8
|
+
class SkeletonProcedure
|
|
9
|
+
|
|
10
|
+
attr_reader :oid, :name, :type, :arg_types, :arg_modes, :arg_names,
|
|
11
|
+
:strict, :setof, :volatility, :secdef, :identifier, :prokind, :language
|
|
12
|
+
|
|
13
|
+
def initialize(oid, name, strict, secdef, setof, type, volatility, arg_modes, arg_names, arg_types, arg_defaults, prokind = "f", language = "plpgsql")
|
|
14
|
+
@oid, @name, @strict, @secdef, @type, @volatility, @setof, @arg_modes, @arg_names, @arg_types, @arg_defaults, @prokind, @language =
|
|
15
|
+
oid, name, strict, secdef, type, volatility, setof, arg_modes, arg_names, arg_types, arg_defaults, prokind, language
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@identifier = Digest::MD5.hexdigest(signature)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Returns source text for argument list
|
|
22
|
+
# @return [String]
|
|
23
|
+
def arguments
|
|
24
|
+
@arg_types.zip(@arg_names, @arg_modes, @arg_defaults).map do |type, name, mode, default|
|
|
25
|
+
"#{mode + " " if mode}#{name.quote + " " if name}#{type.quote}#{" default " + default if default}"
|
|
26
|
+
end.join(", ")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Returns source text for return type
|
|
30
|
+
# @return [String]
|
|
31
|
+
def setof
|
|
32
|
+
@setof ? "setof " : nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns source text for strictness
|
|
36
|
+
# @return [String]
|
|
37
|
+
def strictness
|
|
38
|
+
@strict ? "strict" : nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns source text for security
|
|
42
|
+
# @return [String]
|
|
43
|
+
def security
|
|
44
|
+
@secdef ? "security definer" : nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns source SQL function/procedure definition statement
|
|
48
|
+
# @return [String]
|
|
49
|
+
def definition(body)
|
|
50
|
+
if @prokind == 'p'
|
|
51
|
+
# PostgreSQL PROCEDURE (introduced in PG11)
|
|
52
|
+
[%[create or replace procedure #{name.quote} (#{arguments})],
|
|
53
|
+
%[ language plpgsql #{security} as $__PIGGLY__$],
|
|
54
|
+
body,
|
|
55
|
+
%[$__PIGGLY__$]].join("\n")
|
|
56
|
+
else
|
|
57
|
+
# PostgreSQL FUNCTION
|
|
58
|
+
[%[create or replace function #{name.quote} (#{arguments})],
|
|
59
|
+
%[ returns #{setof}#{type.quote} as $__PIGGLY__$],
|
|
60
|
+
body,
|
|
61
|
+
%[$__PIGGLY__$ language plpgsql #{strictness} #{security} #{@volatility}]].join("\n")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [String]
|
|
66
|
+
def signature
|
|
67
|
+
"#{@name}(#{@arg_modes.zip(@arg_types).map{|m,t| "#{m} #{t}" }.join(", ")})"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @return [String]
|
|
71
|
+
def source_path(config)
|
|
72
|
+
config.mkpath("#{config.cache_root}/Dumper", "#{@identifier}.plpgsql")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @return [String]
|
|
76
|
+
def load_source(config)
|
|
77
|
+
File.read(source_path(config), encoding: 'UTF-8')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @return [String]
|
|
81
|
+
alias source load_source
|
|
82
|
+
|
|
83
|
+
# @return [void]
|
|
84
|
+
def purge_source(config)
|
|
85
|
+
path = source_path(config)
|
|
86
|
+
|
|
87
|
+
FileUtils.rm_r(path) if File.exist?(path)
|
|
88
|
+
|
|
89
|
+
file = Compiler::TraceCompiler.new(config).cache_path(path)
|
|
90
|
+
FileUtils.rm_r(file) if File.exist?(file)
|
|
91
|
+
|
|
92
|
+
file = Reporter::Base.new(config).report_path(path, ".html")
|
|
93
|
+
FileUtils.rm_r(file) if File.exist?(file)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @return [SkeletonProcedure]
|
|
97
|
+
def skeleton
|
|
98
|
+
self
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def skeleton?
|
|
102
|
+
true
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def ==(other)
|
|
106
|
+
other.is_a?(self.class) and
|
|
107
|
+
other.identifier == identifier
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
end
|
|
112
|
+
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,137 @@
|
|
|
1
|
+
module Piggly
|
|
2
|
+
|
|
3
|
+
class Installer
|
|
4
|
+
def initialize(config, connection)
|
|
5
|
+
@config, @connection = config, connection
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# @return [void]
|
|
9
|
+
def install(procedures, profile)
|
|
10
|
+
@connection.exec("begin")
|
|
11
|
+
|
|
12
|
+
install_support(profile)
|
|
13
|
+
|
|
14
|
+
procedures.each do |p|
|
|
15
|
+
begin
|
|
16
|
+
trace(p, profile)
|
|
17
|
+
rescue Parser::Failure
|
|
18
|
+
$stdout.puts $!
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
@connection.exec("commit")
|
|
23
|
+
rescue
|
|
24
|
+
@connection.exec("rollback")
|
|
25
|
+
raise
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [void]
|
|
29
|
+
def uninstall(procedures)
|
|
30
|
+
@connection.exec("begin")
|
|
31
|
+
|
|
32
|
+
procedures.each{|p| untrace(p) }
|
|
33
|
+
uninstall_support
|
|
34
|
+
|
|
35
|
+
@connection.exec("commit")
|
|
36
|
+
rescue
|
|
37
|
+
@connection.exec("rollback")
|
|
38
|
+
raise
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [void]
|
|
42
|
+
def trace(procedure, profile)
|
|
43
|
+
# recompile with instrumentation
|
|
44
|
+
compiler = Compiler::TraceCompiler.new(@config)
|
|
45
|
+
result = compiler.compile(procedure)
|
|
46
|
+
# result[:tree] - tagged and rewritten parse tree
|
|
47
|
+
# result[:tags] - collection of Tag values in the tree
|
|
48
|
+
# result[:code] - instrumented
|
|
49
|
+
|
|
50
|
+
@connection.exec(procedure.definition(result[:code]))
|
|
51
|
+
|
|
52
|
+
profile.add(procedure, result[:tags], result)
|
|
53
|
+
rescue
|
|
54
|
+
$!.message << "\nError installing traced procedure #{procedure.name} "
|
|
55
|
+
$!.message << "from #{procedure.source_path(@config)}"
|
|
56
|
+
raise
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @return [void]
|
|
60
|
+
def untrace(procedure)
|
|
61
|
+
@connection.exec(procedure.definition(procedure.source(@config)))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Installs necessary instrumentation support
|
|
65
|
+
def install_support(profile)
|
|
66
|
+
@connection.set_notice_processor(&profile.notice_processor(@config))
|
|
67
|
+
|
|
68
|
+
# def connection.set_notice_processor
|
|
69
|
+
# # do nothing: prevent the notice processor from being subverted
|
|
70
|
+
# end
|
|
71
|
+
|
|
72
|
+
# install tracing functions
|
|
73
|
+
@connection.exec <<-SQL
|
|
74
|
+
-- Signals that a conditional expression was executed
|
|
75
|
+
CREATE OR REPLACE FUNCTION piggly_cond(message varchar, value boolean)
|
|
76
|
+
RETURNS boolean AS $$
|
|
77
|
+
BEGIN
|
|
78
|
+
IF value THEN
|
|
79
|
+
RAISE WARNING '#{@config.trace_prefix} % t', message;
|
|
80
|
+
ELSE
|
|
81
|
+
RAISE WARNING '#{@config.trace_prefix} % f', message;
|
|
82
|
+
END IF;
|
|
83
|
+
RETURN value;
|
|
84
|
+
END $$ LANGUAGE 'plpgsql' VOLATILE;
|
|
85
|
+
SQL
|
|
86
|
+
|
|
87
|
+
@connection.exec <<-SQL
|
|
88
|
+
-- Generic signal
|
|
89
|
+
CREATE OR REPLACE FUNCTION piggly_signal(message varchar, signal varchar)
|
|
90
|
+
RETURNS void AS $$
|
|
91
|
+
BEGIN
|
|
92
|
+
RAISE WARNING '#{@config.trace_prefix} % %', message, signal;
|
|
93
|
+
END $$ LANGUAGE 'plpgsql' VOLATILE;
|
|
94
|
+
SQL
|
|
95
|
+
|
|
96
|
+
@connection.exec <<-SQL
|
|
97
|
+
-- Signals that a (sub)expression was executed. handles '' and NULL value
|
|
98
|
+
CREATE OR REPLACE FUNCTION piggly_expr(message varchar, value varchar)
|
|
99
|
+
RETURNS varchar AS $$
|
|
100
|
+
BEGIN
|
|
101
|
+
RAISE WARNING '#{@config.trace_prefix} %', message;
|
|
102
|
+
RETURN value;
|
|
103
|
+
END $$ LANGUAGE 'plpgsql' VOLATILE;
|
|
104
|
+
SQL
|
|
105
|
+
|
|
106
|
+
@connection.exec <<-SQL
|
|
107
|
+
-- Signals that a (sub)expression was executed. handles all other types
|
|
108
|
+
CREATE OR REPLACE FUNCTION piggly_expr(message varchar, value anyelement)
|
|
109
|
+
RETURNS anyelement AS $$
|
|
110
|
+
BEGIN
|
|
111
|
+
RAISE WARNING '#{@config.trace_prefix} %', message;
|
|
112
|
+
RETURN value;
|
|
113
|
+
END $$ LANGUAGE 'plpgsql' VOLATILE;
|
|
114
|
+
SQL
|
|
115
|
+
|
|
116
|
+
@connection.exec <<-SQL
|
|
117
|
+
-- Signals that a branch was taken
|
|
118
|
+
CREATE OR REPLACE FUNCTION piggly_branch(message varchar)
|
|
119
|
+
RETURNS void AS $$
|
|
120
|
+
BEGIN
|
|
121
|
+
RAISE WARNING '#{@config.trace_prefix} %', message;
|
|
122
|
+
END $$ LANGUAGE 'plpgsql' VOLATILE;
|
|
123
|
+
SQL
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Uninstalls instrumentation support
|
|
127
|
+
def uninstall_support
|
|
128
|
+
@connection.set_notice_processor{|x| $stderr.puts x }
|
|
129
|
+
@connection.exec "DROP FUNCTION IF EXISTS piggly_cond(varchar, boolean)"
|
|
130
|
+
@connection.exec "DROP FUNCTION IF EXISTS piggly_expr(varchar, varchar)"
|
|
131
|
+
@connection.exec "DROP FUNCTION IF EXISTS piggly_expr(varchar, anyelement)"
|
|
132
|
+
@connection.exec "DROP FUNCTION IF EXISTS piggly_branch(varchar)"
|
|
133
|
+
@connection.exec "DROP FUNCTION IF EXISTS piggly_signal(varchar, varchar)"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
end
|