piggly-nsd 2.3.4 → 2.3.6
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 +4 -4
- data/README.md +17 -3
- data/lib/piggly/command/report.rb +25 -7
- data/lib/piggly/compiler/coverage_report.rb +1 -1
- data/lib/piggly/compiler/line_coverage.rb +7 -9
- data/lib/piggly/dumper/qualified_type.rb +3 -3
- data/lib/piggly/dumper/reified_procedure.rb +1 -1
- data/lib/piggly/parser/grammar.tt +761 -748
- data/lib/piggly/reporter/schema_csv.rb +98 -0
- data/lib/piggly/reporter/sonar.rb +6 -1
- data/lib/piggly/reporter.rb +1 -0
- data/lib/piggly/task.rb +4 -1
- data/lib/piggly/util/line_numbers.rb +25 -0
- data/lib/piggly/util.rb +1 -0
- data/lib/piggly/version.rb +2 -2
- data/spec/examples/dumper/definition_spec.rb +49 -0
- data/spec/examples/dumper/procedure_spec.rb +1 -1
- data/spec/examples/grammar/statements/declaration_spec.rb +12 -0
- data/spec/examples/grammar/statements/if_spec.rb +6 -0
- data/spec/examples/grammar/statements/loop_spec.rb +9 -0
- data/spec/examples/grammar/statements/sql_spec.rb +22 -0
- data/spec/examples/reporter/schema_csv_spec.rb +72 -0
- data/spec/examples/util/line_numbers_spec.rb +59 -0
- data/spec/issues/037_spec.rb +30 -0
- data/spec/issues/case_in_condition_spec.rb +50 -0
- data/spec/issues/commit_spec.rb +34 -0
- data/spec/spec_helper.rb +1 -1
- metadata +24 -2
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
require "csv"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
|
|
4
|
+
module Piggly
|
|
5
|
+
module Reporter
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# Generates schema-level coverage summary in CSV format.
|
|
9
|
+
#
|
|
10
|
+
class SchemaCsv < Base
|
|
11
|
+
HEADERS = ["No", "Schema Name", "Objects Count", "Covered Objects", "Line Coverage Percent"].freeze
|
|
12
|
+
NO_SCHEMA = "<no schema>".freeze
|
|
13
|
+
|
|
14
|
+
def initialize(config, profile, output_path = nil)
|
|
15
|
+
@config = config
|
|
16
|
+
@profile = profile
|
|
17
|
+
@output_path = output_path || File.join(@config.report_root, "coverage_by_schema.csv")
|
|
18
|
+
@line_coverage = Compiler::LineCoverage.new(config)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Generate CSV schema coverage report for all procedures
|
|
22
|
+
# @param procedures [Array<Dumper::ReifiedProcedure>] list of procedures
|
|
23
|
+
def report(procedures)
|
|
24
|
+
FileUtils.makedirs(File.dirname(@output_path))
|
|
25
|
+
aggregates = aggregate_by_schema(procedures)
|
|
26
|
+
|
|
27
|
+
CSV.open(@output_path, "wb:UTF-8") do |csv|
|
|
28
|
+
csv << HEADERS
|
|
29
|
+
|
|
30
|
+
aggregates.each_with_index do |row, index|
|
|
31
|
+
csv << [
|
|
32
|
+
index + 1,
|
|
33
|
+
row[:schema_name],
|
|
34
|
+
row[:objects_count],
|
|
35
|
+
row[:covered_objects],
|
|
36
|
+
format("%0.2f", row[:coverage_percent])
|
|
37
|
+
]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
@output_path
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def aggregate_by_schema(procedures)
|
|
47
|
+
grouped = Hash.new do |hash, key|
|
|
48
|
+
hash[key] = {
|
|
49
|
+
schema_name: key,
|
|
50
|
+
objects_count: 0,
|
|
51
|
+
covered_objects: 0,
|
|
52
|
+
coverage_values: []
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
procedures.each do |procedure|
|
|
57
|
+
schema_name = schema_label(procedure)
|
|
58
|
+
row = grouped[schema_name]
|
|
59
|
+
row[:objects_count] += 1
|
|
60
|
+
|
|
61
|
+
begin
|
|
62
|
+
percent = line_coverage_percent(procedure)
|
|
63
|
+
next if percent.nil?
|
|
64
|
+
|
|
65
|
+
row[:covered_objects] += 1 if percent > 0.0
|
|
66
|
+
row[:coverage_values] << percent
|
|
67
|
+
rescue => e
|
|
68
|
+
$stderr.puts "Warning: Could not calculate schema CSV coverage for #{procedure.name}: #{e.message}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
grouped.keys.sort.map do |schema_name|
|
|
73
|
+
row = grouped[schema_name]
|
|
74
|
+
values = row[:coverage_values]
|
|
75
|
+
average = values.empty? ? 0.0 : values.inject(0.0, :+) / values.size
|
|
76
|
+
|
|
77
|
+
{
|
|
78
|
+
schema_name: row[:schema_name],
|
|
79
|
+
objects_count: row[:objects_count],
|
|
80
|
+
covered_objects: row[:covered_objects],
|
|
81
|
+
coverage_percent: average
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def schema_label(procedure)
|
|
87
|
+
schema = procedure.name.schema.to_s.strip
|
|
88
|
+
schema.empty? ? NO_SCHEMA : schema
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def line_coverage_percent(procedure)
|
|
92
|
+
coverage = @line_coverage.calculate(procedure, @profile)
|
|
93
|
+
@line_coverage.summary(coverage)[:percent]
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -52,9 +52,14 @@ module Piggly
|
|
|
52
52
|
source_path = make_relative_path(absolute_path)
|
|
53
53
|
|
|
54
54
|
io.puts " <file path=\"#{escape_xml(source_path)}\">"
|
|
55
|
-
|
|
55
|
+
|
|
56
|
+
source = procedure.source(@config)
|
|
57
|
+
max_line = Util::LineNumbers.count(source)
|
|
58
|
+
|
|
56
59
|
# Sort lines and output coverage data
|
|
57
60
|
coverage.keys.sort.each do |line|
|
|
61
|
+
next if line < 1 || line > max_line
|
|
62
|
+
|
|
58
63
|
line_data = coverage[line]
|
|
59
64
|
write_line_coverage(io, line, line_data)
|
|
60
65
|
end
|
data/lib/piggly/reporter.rb
CHANGED
data/lib/piggly/task.rb
CHANGED
|
@@ -118,12 +118,14 @@ module Piggly
|
|
|
118
118
|
class ReportTask < AbstractTask
|
|
119
119
|
attr_accessor :report_root, # Where to store reports (default piggly/report)
|
|
120
120
|
:accumulate, # Accumulate coverage from the previous run (default false)
|
|
121
|
-
:trace_file
|
|
121
|
+
:trace_file,
|
|
122
|
+
:schema_csv_path
|
|
122
123
|
|
|
123
124
|
def initialize(name = :report)
|
|
124
125
|
@accumulate = false
|
|
125
126
|
@trace_file = nil
|
|
126
127
|
@report_root = nil
|
|
128
|
+
@schema_csv_path = nil
|
|
127
129
|
super(name)
|
|
128
130
|
end
|
|
129
131
|
|
|
@@ -141,6 +143,7 @@ module Piggly
|
|
|
141
143
|
opts.concat(["--trace-file", @trace_file])
|
|
142
144
|
opts.concat(["--cache-root", @cache_root]) if @cache_root
|
|
143
145
|
opts.concat(["--report-root", @report_root]) if @report_root
|
|
146
|
+
opts.concat(["--schema-csv-path", @schema_csv_path]) if @schema_csv_path
|
|
144
147
|
|
|
145
148
|
case @procedures
|
|
146
149
|
when String then opts.concat(["--name", @procedures])
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Piggly
|
|
2
|
+
module Util
|
|
3
|
+
|
|
4
|
+
module LineNumbers
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def count(source)
|
|
8
|
+
return 0 if source.nil? || source.empty?
|
|
9
|
+
|
|
10
|
+
source.lines.count
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# 1-based line number for a byte offset, clamped to [1, count(source)].
|
|
14
|
+
def at_offset(source, offset)
|
|
15
|
+
return 1 if source.nil? || source.empty?
|
|
16
|
+
|
|
17
|
+
offset = [[offset, 0].max, source.length].min
|
|
18
|
+
line = source[0...offset].count("\n") + 1
|
|
19
|
+
max_line = count(source)
|
|
20
|
+
[[line, 1].max, max_line].min
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/piggly/util.rb
CHANGED
data/lib/piggly/version.rb
CHANGED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
module Piggly
|
|
4
|
+
module Dumper
|
|
5
|
+
|
|
6
|
+
describe SkeletonProcedure, "definition" do
|
|
7
|
+
def qname(schema, name)
|
|
8
|
+
QualifiedName.new(schema, name)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def qtype(*args)
|
|
12
|
+
QualifiedType.parse(*args)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "quotes user-defined types but not built-in types" do
|
|
16
|
+
expect(qtype("character varying").quote).to eq("varchar")
|
|
17
|
+
expect(qtype("pg_catalog", "varchar").quote).to eq("varchar")
|
|
18
|
+
expect(qtype("bigint").quote).to eq("bigint")
|
|
19
|
+
expect(qtype("private", "mytype").quote).to eq('"private"."mytype"')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "generates valid SQL for functions with table return columns" do
|
|
23
|
+
procedure = ReifiedProcedure.new(
|
|
24
|
+
"begin end;",
|
|
25
|
+
1,
|
|
26
|
+
qname("public", "f_test_function"),
|
|
27
|
+
false,
|
|
28
|
+
false,
|
|
29
|
+
true,
|
|
30
|
+
qtype("pg_catalog", "varchar"),
|
|
31
|
+
"volatile",
|
|
32
|
+
%w[in in in t],
|
|
33
|
+
%w[p_payment_session_id p_payment_type_start p_payment_type_end type_mnemo].map { |n| qname(nil, n) },
|
|
34
|
+
["bigint", "character varying", "character varying", "character varying"].map { |t| qtype(t) },
|
|
35
|
+
[nil, nil, nil, nil]
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
sql = procedure.definition("begin end;")
|
|
39
|
+
|
|
40
|
+
expect(sql).to include('in "p_payment_session_id" bigint')
|
|
41
|
+
expect(sql).to include('in "p_payment_type_start" varchar')
|
|
42
|
+
expect(sql).to include('returns table ("type_mnemo" varchar)')
|
|
43
|
+
expect(sql).not_to include('t "type_mnemo"')
|
|
44
|
+
expect(sql).not_to include('"varchar"')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -15,6 +15,18 @@ module Piggly
|
|
|
15
15
|
it "allows an initial assignment" do
|
|
16
16
|
node = parse(:stmtDeclare, "declare a text := 10;")
|
|
17
17
|
end
|
|
18
|
+
|
|
19
|
+
it "parses parameterized cursor declarations" do
|
|
20
|
+
parse(:stmtDeclare, <<~SQL)
|
|
21
|
+
declare
|
|
22
|
+
c_lic_acc_value cursor(c_lic_acc_id bigint, c_date_old timestamp) for
|
|
23
|
+
select date_v, beg_val from public.t_table_value where link_lic_acc = c_lic_acc_id;
|
|
24
|
+
SQL
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "parses cursor declarations without space before parameter list" do
|
|
28
|
+
parse(:stmtDeclare, "declare c cursor(x int) for select 1;")
|
|
29
|
+
end
|
|
18
30
|
end
|
|
19
31
|
|
|
20
32
|
end
|
|
@@ -62,6 +62,12 @@ module Piggly
|
|
|
62
62
|
node.should be_statement
|
|
63
63
|
node.count{|e| e.comment? }.should == 3
|
|
64
64
|
end
|
|
65
|
+
|
|
66
|
+
it "allows then immediately after closing parenthesis" do
|
|
67
|
+
node, rest = parse_some(:statement, 'IF exists(select 1)then null; end if;')
|
|
68
|
+
node.should be_statement
|
|
69
|
+
rest.should == ''
|
|
70
|
+
end
|
|
65
71
|
end
|
|
66
72
|
|
|
67
73
|
describe "if .. then .. else .. end if" do
|
|
@@ -23,6 +23,15 @@ module Piggly
|
|
|
23
23
|
cond.source_text.should == 'SELECT * FROM table '
|
|
24
24
|
cond.should be_sql
|
|
25
25
|
end
|
|
26
|
+
|
|
27
|
+
it "can loop over parenthesized query without whitespace after IN" do
|
|
28
|
+
node = parse(:stmtForLoop, 'FOR x IN(SELECT * FROM table) LOOP a := x; END LOOP;')
|
|
29
|
+
node.should be_statement
|
|
30
|
+
|
|
31
|
+
cond = node.find{|e| e.named?(:cond) }
|
|
32
|
+
cond.source_text.should == '(SELECT * FROM table) '
|
|
33
|
+
cond.should be_expression
|
|
34
|
+
end
|
|
26
35
|
end
|
|
27
36
|
|
|
28
37
|
describe "while loops" do
|
|
@@ -13,6 +13,28 @@ module Piggly
|
|
|
13
13
|
rest.should == ''
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
it "parses ANALYZE statements" do
|
|
17
|
+
node, rest = parse_some(:statement, 'ANALYZE tt_test;')
|
|
18
|
+
node.should be_statement
|
|
19
|
+
node.count{|e| e.sql? }.should == 1
|
|
20
|
+
node.find{|e| e.sql? }.source_text.should == 'ANALYZE tt_test;'
|
|
21
|
+
rest.should == ''
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "parses MERGE statements" do
|
|
25
|
+
node, rest = parse_some(:statement, <<-SQL.strip)
|
|
26
|
+
MERGE INTO users u
|
|
27
|
+
USING (SELECT 1 AS id) s
|
|
28
|
+
ON s.id = u.id
|
|
29
|
+
WHEN NOT MATCHED THEN
|
|
30
|
+
INSERT (id) VALUES (s.id);
|
|
31
|
+
SQL
|
|
32
|
+
node.should be_statement
|
|
33
|
+
node.count{|e| e.sql? }.should == 1
|
|
34
|
+
node.find{|e| e.sql? }.source_text.should =~ /\Amerge into users/mi
|
|
35
|
+
rest.should == ''
|
|
36
|
+
end
|
|
37
|
+
|
|
16
38
|
it "must end with a semicolon" do
|
|
17
39
|
expect{ parse(:statement, 'SELECT id FROM users') }.to raise_error(Piggly::Parser::Failure)
|
|
18
40
|
expect{ parse_some(:statement, 'SELECT id FROM users') }.to raise_error(Piggly::Parser::Failure)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "csv"
|
|
3
|
+
require "tmpdir"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Piggly
|
|
7
|
+
|
|
8
|
+
describe Reporter::SchemaCsv do
|
|
9
|
+
let(:profile) { double(:profile) }
|
|
10
|
+
let(:line_coverage) { double(:line_coverage) }
|
|
11
|
+
let(:tmpdir) { Dir.mktmpdir("piggly-schema-csv") }
|
|
12
|
+
let(:config) { double(:config, :report_root => tmpdir) }
|
|
13
|
+
let(:output_path) { File.join(tmpdir, "schema-coverage.csv") }
|
|
14
|
+
|
|
15
|
+
def procedure(schema, name)
|
|
16
|
+
qualified_name = Dumper::QualifiedName.new(schema, name)
|
|
17
|
+
double(:procedure, :name => qualified_name)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
before do
|
|
21
|
+
allow(Compiler::LineCoverage).to receive(:new).with(config).and_return(line_coverage)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
after do
|
|
25
|
+
FileUtils.remove_entry(tmpdir) if File.exist?(tmpdir)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "writes schema coverage CSV with headers and aggregated rows" do
|
|
29
|
+
first_public = procedure("public", "alpha")
|
|
30
|
+
second_public = procedure("public", "beta")
|
|
31
|
+
app_proc = procedure("app", "gamma")
|
|
32
|
+
no_schema_proc = procedure(nil, "delta")
|
|
33
|
+
|
|
34
|
+
allow(line_coverage).to receive(:calculate).with(first_public, profile).and_return(:cov_public_1)
|
|
35
|
+
allow(line_coverage).to receive(:calculate).with(second_public, profile).and_return(:cov_public_2)
|
|
36
|
+
allow(line_coverage).to receive(:calculate).with(app_proc, profile).and_return(:cov_app)
|
|
37
|
+
allow(line_coverage).to receive(:calculate).with(no_schema_proc, profile).and_return(:cov_no_schema)
|
|
38
|
+
|
|
39
|
+
allow(line_coverage).to receive(:summary).with(:cov_public_1).and_return(:percent => 100.0)
|
|
40
|
+
allow(line_coverage).to receive(:summary).with(:cov_public_2).and_return(:percent => 0.0)
|
|
41
|
+
allow(line_coverage).to receive(:summary).with(:cov_app).and_return(:percent => nil)
|
|
42
|
+
allow(line_coverage).to receive(:summary).with(:cov_no_schema).and_return(:percent => 75.0)
|
|
43
|
+
|
|
44
|
+
reporter = Reporter::SchemaCsv.new(config, profile, output_path)
|
|
45
|
+
result_path = reporter.report([first_public, second_public, app_proc, no_schema_proc])
|
|
46
|
+
|
|
47
|
+
expect(result_path).to eq(output_path)
|
|
48
|
+
expect(File).to exist(output_path)
|
|
49
|
+
|
|
50
|
+
rows = CSV.read(output_path, :headers => true)
|
|
51
|
+
|
|
52
|
+
expect(rows.headers).to eq(["No", "Schema Name", "Objects Count", "Covered Objects", "Line Coverage Percent"])
|
|
53
|
+
|
|
54
|
+
by_schema = rows.each_with_object({}) do |row, hash|
|
|
55
|
+
hash[row["Schema Name"]] = row
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
expect(by_schema.fetch("public")["Objects Count"]).to eq("2")
|
|
59
|
+
expect(by_schema.fetch("public")["Covered Objects"]).to eq("1")
|
|
60
|
+
expect(by_schema.fetch("public")["Line Coverage Percent"]).to eq("50.00")
|
|
61
|
+
|
|
62
|
+
expect(by_schema.fetch("app")["Objects Count"]).to eq("1")
|
|
63
|
+
expect(by_schema.fetch("app")["Covered Objects"]).to eq("0")
|
|
64
|
+
expect(by_schema.fetch("app")["Line Coverage Percent"]).to eq("0.00")
|
|
65
|
+
|
|
66
|
+
expect(by_schema.fetch("<no schema>")["Objects Count"]).to eq("1")
|
|
67
|
+
expect(by_schema.fetch("<no schema>")["Covered Objects"]).to eq("1")
|
|
68
|
+
expect(by_schema.fetch("<no schema>")["Line Coverage Percent"]).to eq("75.00")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Piggly::Util
|
|
4
|
+
|
|
5
|
+
describe LineNumbers do
|
|
6
|
+
describe ".count" do
|
|
7
|
+
it "returns 0 for empty source" do
|
|
8
|
+
expect(LineNumbers.count("")).to eq(0)
|
|
9
|
+
expect(LineNumbers.count(nil)).to eq(0)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "counts lines without a trailing newline" do
|
|
13
|
+
expect(LineNumbers.count("a\nb\nc")).to eq(3)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "does not over-count when source ends with a newline" do
|
|
17
|
+
expect(LineNumbers.count("a\nb\nc\n")).to eq(3)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "counts CRLF sources consistently" do
|
|
21
|
+
expect(LineNumbers.count("a\r\nb\r\nc\r\n")).to eq(3)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "matches String#lines for a large trailing-newline body" do
|
|
25
|
+
source = (1..366).map { |i| "line#{i}" }.join("\n") + "\n"
|
|
26
|
+
expect(LineNumbers.count(source)).to eq(366)
|
|
27
|
+
expect(LineNumbers.count(source)).to eq(source.lines.count)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe ".at_offset" do
|
|
32
|
+
let(:source) { "a\nb\nc\n" }
|
|
33
|
+
|
|
34
|
+
it "returns 1 for empty source" do
|
|
35
|
+
expect(LineNumbers.at_offset("", 0)).to eq(1)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "maps byte offsets to 1-based line numbers" do
|
|
39
|
+
expect(LineNumbers.at_offset(source, 0)).to eq(1)
|
|
40
|
+
expect(LineNumbers.at_offset(source, 2)).to eq(2)
|
|
41
|
+
expect(LineNumbers.at_offset(source, 4)).to eq(3)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "clamps offsets past EOF to the last line" do
|
|
45
|
+
expect(LineNumbers.at_offset(source, source.length)).to eq(3)
|
|
46
|
+
expect(LineNumbers.at_offset(source, source.length + 10)).to eq(3)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "does not report a line beyond count when offset is on trailing newline" do
|
|
50
|
+
source = (1..366).map { |i| "line#{i}" }.join("\n") + "\n"
|
|
51
|
+
max_line = LineNumbers.count(source)
|
|
52
|
+
|
|
53
|
+
expect(LineNumbers.at_offset(source, source.length - 1)).to eq(max_line)
|
|
54
|
+
expect(LineNumbers.at_offset(source, source.length)).to eq(max_line)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
module Piggly
|
|
4
|
+
describe "GET CURRENT DIAGNOSTICS" do
|
|
5
|
+
include GrammarHelper
|
|
6
|
+
|
|
7
|
+
it "can parse a GET CURRENT DIAGNOSTICS statement" do
|
|
8
|
+
body = "GET CURRENT DIAGNOSTICS l_wdc_inserted := ROW_COUNT;"
|
|
9
|
+
|
|
10
|
+
node = parse(:statement, body)
|
|
11
|
+
node.should be_statement
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "can parse a procedure with GET CURRENT DIAGNOSTICS" do
|
|
15
|
+
body = <<-SQL
|
|
16
|
+
DECLARE
|
|
17
|
+
l_wdc_inserted bigint;
|
|
18
|
+
BEGIN
|
|
19
|
+
INSERT INTO foo DEFAULT VALUES;
|
|
20
|
+
GET CURRENT DIAGNOSTICS l_wdc_inserted := ROW_COUNT;
|
|
21
|
+
RETURN l_wdc_inserted;
|
|
22
|
+
END;
|
|
23
|
+
SQL
|
|
24
|
+
|
|
25
|
+
node = parse(:start, body.strip.downcase)
|
|
26
|
+
node.count { |e| e.assignment? }.should == 0
|
|
27
|
+
node.count { |e| Parser::Nodes::Return === e }.should == 1
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
module Piggly
|
|
4
|
+
describe "SQL CASE in PL/pgSQL conditions" do
|
|
5
|
+
include GrammarHelper
|
|
6
|
+
|
|
7
|
+
it "can parse IF with CASE in a function argument" do
|
|
8
|
+
body = <<-SQL
|
|
9
|
+
BEGIN
|
|
10
|
+
IF 0 = public.f_test(
|
|
11
|
+
p_doc_type_mnemo => line_rec.doc_type_mnemo,
|
|
12
|
+
p_person_id => CASE
|
|
13
|
+
WHEN l_f_sub_acc = 1 THEN p_client_id
|
|
14
|
+
WHEN l_f_sub_acc = 0 THEN l_person_id
|
|
15
|
+
END,
|
|
16
|
+
p_repres_id => NULL
|
|
17
|
+
) THEN
|
|
18
|
+
NULL;
|
|
19
|
+
END IF;
|
|
20
|
+
END;
|
|
21
|
+
SQL
|
|
22
|
+
|
|
23
|
+
node = parse(:start, body.strip.downcase)
|
|
24
|
+
node.count { |e| e.if? }.should == 1
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "can parse ELSIF with CASE in a function argument" do
|
|
28
|
+
body = <<-SQL
|
|
29
|
+
BEGIN
|
|
30
|
+
IF false THEN
|
|
31
|
+
NULL;
|
|
32
|
+
ELSIF 0 = public.f_test(
|
|
33
|
+
p_person_id => CASE WHEN l_flag = 1 THEN p_a ELSE p_b END
|
|
34
|
+
) THEN
|
|
35
|
+
NULL;
|
|
36
|
+
END IF;
|
|
37
|
+
END;
|
|
38
|
+
SQL
|
|
39
|
+
|
|
40
|
+
node = parse(:start, body.strip.downcase)
|
|
41
|
+
node.count { |e| e.if? }.should == 2
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "does not break simple IF conditions" do
|
|
45
|
+
node = parse(:statement, "IF cond THEN a := 10; END IF;")
|
|
46
|
+
node.should be_statement
|
|
47
|
+
node.count { |e| e.if? }.should == 1
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
module Piggly
|
|
4
|
+
describe "standalone transaction statements" do
|
|
5
|
+
include GrammarHelper
|
|
6
|
+
|
|
7
|
+
it "can parse commit without trailing expression" do
|
|
8
|
+
node = parse(:statement, "commit;")
|
|
9
|
+
node.should be_statement
|
|
10
|
+
node.count { |e| e.sql? }.should == 1
|
|
11
|
+
node.find { |e| e.sql? }.source_text.should == "commit;"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "can parse rollback without trailing expression" do
|
|
15
|
+
node = parse(:statement, "rollback;")
|
|
16
|
+
node.should be_statement
|
|
17
|
+
node.count { |e| e.sql? }.should == 1
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "can parse a procedure with multiple commit statements" do
|
|
21
|
+
body = <<-SQL
|
|
22
|
+
BEGIN
|
|
23
|
+
call i_schema.create_choice1(p_session_id => l_session_id);
|
|
24
|
+
commit;
|
|
25
|
+
call i_schema.create_choice2(l_session_id);
|
|
26
|
+
commit;
|
|
27
|
+
END;
|
|
28
|
+
SQL
|
|
29
|
+
|
|
30
|
+
node = parse(:start, body.strip.downcase)
|
|
31
|
+
node.count { |e| e.sql? }.should == 4
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -26,7 +26,7 @@ module Piggly
|
|
|
26
26
|
|
|
27
27
|
COMMENTS = ["abc defghi", "abc -- abc", "quote's", "a 'str'"]
|
|
28
28
|
|
|
29
|
-
SQLWORDS = %w[select insert update delete drop alter commit set start]
|
|
29
|
+
SQLWORDS = %w[select insert update delete merge drop alter commit set start]
|
|
30
30
|
|
|
31
31
|
KEYWORDS = %w[as := = alias begin by constant continue
|
|
32
32
|
cursor debug declare diagnostics else elsif elseif
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: piggly-nsd
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.3.
|
|
4
|
+
version: 2.3.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kvle Putnam
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: 0.18.4
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: csv
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '3.3'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.3'
|
|
40
54
|
description: PostgreSQL PL/pgSQL stored procedure code coverage (NSD fork)
|
|
41
55
|
email: putnam.kvle@gmail.com
|
|
42
56
|
executables:
|
|
@@ -80,6 +94,7 @@ files:
|
|
|
80
94
|
- lib/piggly/reporter/resources/highlight.js
|
|
81
95
|
- lib/piggly/reporter/resources/piggly.css
|
|
82
96
|
- lib/piggly/reporter/resources/sortable.js
|
|
97
|
+
- lib/piggly/reporter/schema_csv.rb
|
|
83
98
|
- lib/piggly/reporter/sonar.rb
|
|
84
99
|
- lib/piggly/tags.rb
|
|
85
100
|
- lib/piggly/task.rb
|
|
@@ -88,6 +103,7 @@ files:
|
|
|
88
103
|
- lib/piggly/util/cacheable.rb
|
|
89
104
|
- lib/piggly/util/enumerable.rb
|
|
90
105
|
- lib/piggly/util/file.rb
|
|
106
|
+
- lib/piggly/util/line_numbers.rb
|
|
91
107
|
- lib/piggly/util/process_queue.rb
|
|
92
108
|
- lib/piggly/util/thunk.rb
|
|
93
109
|
- lib/piggly/version.rb
|
|
@@ -95,6 +111,7 @@ files:
|
|
|
95
111
|
- spec/examples/compiler/report_spec.rb
|
|
96
112
|
- spec/examples/compiler/trace_spec.rb
|
|
97
113
|
- spec/examples/config_spec.rb
|
|
114
|
+
- spec/examples/dumper/definition_spec.rb
|
|
98
115
|
- spec/examples/dumper/index_spec.rb
|
|
99
116
|
- spec/examples/dumper/procedure_spec.rb
|
|
100
117
|
- spec/examples/grammar/expression_spec.rb
|
|
@@ -123,12 +140,14 @@ files:
|
|
|
123
140
|
- spec/examples/reporter/html/dsl_spec.rb
|
|
124
141
|
- spec/examples/reporter/html/index_spec.rb
|
|
125
142
|
- spec/examples/reporter/html_spec.rb
|
|
143
|
+
- spec/examples/reporter/schema_csv_spec.rb
|
|
126
144
|
- spec/examples/reporter_spec.rb
|
|
127
145
|
- spec/examples/tags_spec.rb
|
|
128
146
|
- spec/examples/task_spec.rb
|
|
129
147
|
- spec/examples/util/cacheable_spec.rb
|
|
130
148
|
- spec/examples/util/enumerable_spec.rb
|
|
131
149
|
- spec/examples/util/file_spec.rb
|
|
150
|
+
- spec/examples/util/line_numbers_spec.rb
|
|
132
151
|
- spec/examples/util/process_queue_spec.rb
|
|
133
152
|
- spec/examples/util/thunk_spec.rb
|
|
134
153
|
- spec/examples/version_spec.rb
|
|
@@ -138,9 +157,12 @@ files:
|
|
|
138
157
|
- spec/issues/028_spec.rb
|
|
139
158
|
- spec/issues/032_spec.rb
|
|
140
159
|
- spec/issues/036_spec.rb
|
|
160
|
+
- spec/issues/037_spec.rb
|
|
161
|
+
- spec/issues/case_in_condition_spec.rb
|
|
162
|
+
- spec/issues/commit_spec.rb
|
|
141
163
|
- spec/spec_helper.rb
|
|
142
164
|
- spec/spec_suite.rb
|
|
143
|
-
homepage: https://github.com/
|
|
165
|
+
homepage: https://github.com/NSDDeveloper/piggly
|
|
144
166
|
licenses:
|
|
145
167
|
- BSD-2-Clause
|
|
146
168
|
metadata: {}
|