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.
Files changed (96) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +170 -0
  3. data/Rakefile +33 -0
  4. data/bin/piggly +8 -0
  5. data/lib/piggly/command/base.rb +148 -0
  6. data/lib/piggly/command/report.rb +162 -0
  7. data/lib/piggly/command/trace.rb +90 -0
  8. data/lib/piggly/command/untrace.rb +78 -0
  9. data/lib/piggly/command.rb +8 -0
  10. data/lib/piggly/compiler/cache_dir.rb +119 -0
  11. data/lib/piggly/compiler/coverage_report.rb +63 -0
  12. data/lib/piggly/compiler/trace_compiler.rb +117 -0
  13. data/lib/piggly/compiler.rb +7 -0
  14. data/lib/piggly/config.rb +80 -0
  15. data/lib/piggly/dumper/index.rb +121 -0
  16. data/lib/piggly/dumper/qualified_name.rb +36 -0
  17. data/lib/piggly/dumper/qualified_type.rb +141 -0
  18. data/lib/piggly/dumper/reified_procedure.rb +172 -0
  19. data/lib/piggly/dumper/skeleton_procedure.rb +112 -0
  20. data/lib/piggly/dumper.rb +9 -0
  21. data/lib/piggly/installer.rb +137 -0
  22. data/lib/piggly/parser/grammar.tt +748 -0
  23. data/lib/piggly/parser/nodes.rb +378 -0
  24. data/lib/piggly/parser/traversal.rb +50 -0
  25. data/lib/piggly/parser/treetop_ruby19_patch.rb +21 -0
  26. data/lib/piggly/parser.rb +69 -0
  27. data/lib/piggly/profile.rb +108 -0
  28. data/lib/piggly/reporter/base.rb +106 -0
  29. data/lib/piggly/reporter/html_dsl.rb +63 -0
  30. data/lib/piggly/reporter/index.rb +114 -0
  31. data/lib/piggly/reporter/procedure.rb +129 -0
  32. data/lib/piggly/reporter/resources/highlight.js +38 -0
  33. data/lib/piggly/reporter/resources/piggly.css +515 -0
  34. data/lib/piggly/reporter/resources/sortable.js +493 -0
  35. data/lib/piggly/reporter.rb +8 -0
  36. data/lib/piggly/tags.rb +280 -0
  37. data/lib/piggly/task.rb +215 -0
  38. data/lib/piggly/util/blankslate.rb +114 -0
  39. data/lib/piggly/util/cacheable.rb +19 -0
  40. data/lib/piggly/util/enumerable.rb +44 -0
  41. data/lib/piggly/util/file.rb +17 -0
  42. data/lib/piggly/util/process_queue.rb +96 -0
  43. data/lib/piggly/util/thunk.rb +39 -0
  44. data/lib/piggly/util.rb +9 -0
  45. data/lib/piggly/version.rb +15 -0
  46. data/lib/piggly.rb +20 -0
  47. data/spec/examples/compiler/cacheable_spec.rb +190 -0
  48. data/spec/examples/compiler/report_spec.rb +25 -0
  49. data/spec/examples/compiler/trace_spec.rb +123 -0
  50. data/spec/examples/config_spec.rb +63 -0
  51. data/spec/examples/dumper/index_spec.rb +199 -0
  52. data/spec/examples/dumper/procedure_spec.rb +116 -0
  53. data/spec/examples/grammar/expression_spec.rb +302 -0
  54. data/spec/examples/grammar/statements/assignment_spec.rb +70 -0
  55. data/spec/examples/grammar/statements/declaration_spec.rb +21 -0
  56. data/spec/examples/grammar/statements/exception_spec.rb +78 -0
  57. data/spec/examples/grammar/statements/if_spec.rb +191 -0
  58. data/spec/examples/grammar/statements/loop_spec.rb +41 -0
  59. data/spec/examples/grammar/statements/sql_spec.rb +71 -0
  60. data/spec/examples/grammar/tokens/comment_spec.rb +58 -0
  61. data/spec/examples/grammar/tokens/datatype_spec.rb +58 -0
  62. data/spec/examples/grammar/tokens/identifier_spec.rb +74 -0
  63. data/spec/examples/grammar/tokens/keyword_spec.rb +44 -0
  64. data/spec/examples/grammar/tokens/label_spec.rb +40 -0
  65. data/spec/examples/grammar/tokens/literal_spec.rb +30 -0
  66. data/spec/examples/grammar/tokens/lval_spec.rb +50 -0
  67. data/spec/examples/grammar/tokens/number_spec.rb +34 -0
  68. data/spec/examples/grammar/tokens/sqlkeywords_spec.rb +45 -0
  69. data/spec/examples/grammar/tokens/string_spec.rb +54 -0
  70. data/spec/examples/grammar/tokens/whitespace_spec.rb +40 -0
  71. data/spec/examples/installer_spec.rb +59 -0
  72. data/spec/examples/parser/nodes_spec.rb +73 -0
  73. data/spec/examples/parser/traversal_spec.rb +14 -0
  74. data/spec/examples/parser_spec.rb +118 -0
  75. data/spec/examples/profile_spec.rb +153 -0
  76. data/spec/examples/reporter/html/dsl_spec.rb +0 -0
  77. data/spec/examples/reporter/html/index_spec.rb +0 -0
  78. data/spec/examples/reporter/html_spec.rb +1 -0
  79. data/spec/examples/reporter_spec.rb +0 -0
  80. data/spec/examples/tags_spec.rb +285 -0
  81. data/spec/examples/task_spec.rb +0 -0
  82. data/spec/examples/util/cacheable_spec.rb +41 -0
  83. data/spec/examples/util/enumerable_spec.rb +64 -0
  84. data/spec/examples/util/file_spec.rb +40 -0
  85. data/spec/examples/util/process_queue_spec.rb +16 -0
  86. data/spec/examples/util/thunk_spec.rb +59 -0
  87. data/spec/examples/version_spec.rb +0 -0
  88. data/spec/issues/007_spec.rb +25 -0
  89. data/spec/issues/008_spec.rb +73 -0
  90. data/spec/issues/018_spec.rb +25 -0
  91. data/spec/issues/028_spec.rb +48 -0
  92. data/spec/issues/032_spec.rb +98 -0
  93. data/spec/issues/036_spec.rb +41 -0
  94. data/spec/spec_helper.rb +312 -0
  95. data/spec/spec_suite.rb +5 -0
  96. metadata +162 -0
@@ -0,0 +1,199 @@
1
+ require "spec_helper"
2
+
3
+ module Piggly
4
+
5
+ describe Dumper::Index do
6
+ before do
7
+ # make sure not to create directories all over the file system during the test
8
+ allow(Config).to receive(:mkpath) { |root, file| File.join(root, file) }
9
+
10
+ @config = Config.new
11
+ @index = Dumper::Index.new(@config)
12
+ end
13
+
14
+ context "when cache file doesn't exist" do
15
+ it "is empty" do
16
+ expect(File).to receive(:exist?).with(@index.path).and_return(false)
17
+ expect(@index.procedures).to be_empty
18
+ end
19
+ end
20
+
21
+ context "when cache file exists" do
22
+ before do
23
+ allow(File).to receive(:exist?).with(@index.path).and_return(true)
24
+ end
25
+
26
+ context "when the cache index file is empty" do
27
+ it "is empty" do
28
+ allow(File).to receive(:read).and_return([].to_yaml)
29
+ expect(@index.procedures).to be_empty
30
+ end
31
+ end
32
+
33
+ context "when the cache index file has two entries" do
34
+ before do
35
+ @first = Dumper::ReifiedProcedure.from_hash \
36
+ "oid" => "1000",
37
+ "name" => "iterate",
38
+ "source" => "FIRST PROCEDURE SOURCE CODE"
39
+
40
+ @second = Dumper::ReifiedProcedure.from_hash \
41
+ "oid" => "2000",
42
+ "name" => "login",
43
+ "source" => "SECOND PROCEDURE SOURCE CODE"
44
+
45
+ allow(File).to receive(:read) do |path, *args|
46
+ case path
47
+ when @first.source_path(@config)
48
+ @first.source(@config)
49
+ when @second.source_path(@config)
50
+ @second.source(@config)
51
+ when @index.path
52
+ YAML.dump([@first, @second])
53
+ end
54
+ end
55
+ end
56
+
57
+ it "has two procedures" do
58
+ expect(@index.procedures.size).to eq(2)
59
+ end
60
+
61
+ it "is indexed by identifier" do
62
+ expect(@index[@first.identifier].identifier).to eq(@first.identifier)
63
+ expect(@index[@second.identifier].identifier).to eq(@second.identifier)
64
+ end
65
+
66
+ it "reads each procedure's source_path" do
67
+ expect(@index[@first.identifier].source(@config)).to eq(@first.source(@config))
68
+ expect(@index[@second.identifier].source(@config)).to eq(@second.source(@config))
69
+ end
70
+
71
+ context "when the procedures used to be identified using another method" do
72
+ it "renames each procedure using the current identifier"
73
+ it "updates the index with the current identified_using"
74
+ it "writes the updated index to disk"
75
+ end
76
+ end
77
+ end
78
+
79
+ describe "update" do
80
+ it "caches the source of new procedures"
81
+ it "updates the cached source of updated procedures"
82
+ it "purges the cached source of outdated procedures"
83
+ it "writes the cache index to disk"
84
+ it "does not write procedure source code within the cache index"
85
+ end
86
+
87
+ describe "label" do
88
+ def q(*ns)
89
+ Dumper::QualifiedName.new(*ns)
90
+ end
91
+
92
+ before do
93
+ @procedure = double(:oid => 1,
94
+ :name => q("public", "foo"),
95
+ :type => q("private", "int"),
96
+ :arg_modes => ["in", "in"],
97
+ :arg_names => [],
98
+ :arg_types => [q("private", "int"), q("private", "varchar")])
99
+ end
100
+
101
+ context "when name is unique" do
102
+ context "and there is only one schema" do
103
+ before do
104
+ allow(@index).to receive(:procedures).and_return(
105
+ [ @procedure,
106
+ double(:oid => 2,
107
+ :name => q("public", "bar"),
108
+ :type => q("private", "int"),
109
+ :arg_modes => ["in"],
110
+ :arg_names => [],
111
+ :arg_types => []) ])
112
+ end
113
+
114
+ it "specifies schema.name" do
115
+ expect(@index.label(@procedure)).to eq("foo")
116
+ end
117
+ end
118
+
119
+ context "and there is more than one schema" do
120
+ before do
121
+ allow(@index).to receive(:procedures).and_return(
122
+ [ @procedure,
123
+ double(:oid => 2,
124
+ :name => q("schema", "foo"),
125
+ :type => q("private", "int"),
126
+ :arg_modes => ["in"],
127
+ :arg_names => [],
128
+ :arg_types => []) ])
129
+ end
130
+
131
+ it "specifies schema.name" do
132
+ expect(@index.label(@procedure)).to eq("public.foo")
133
+ end
134
+ end
135
+ end
136
+
137
+ context "when name is not unique" do
138
+ context "and schema.name is unique" do
139
+ before do
140
+ allow(@index).to receive(:procedures).and_return(
141
+ [ @procedure,
142
+ double(:oid => 2,
143
+ :name => q("schema", "foo"),
144
+ :type => q("private", "int"),
145
+ :arg_modes => ["in"],
146
+ :arg_names => [],
147
+ :arg_types => []) ])
148
+ end
149
+
150
+ it "specifies schema.name" do
151
+ expect(@index.label(@procedure)).to eq("public.foo")
152
+ end
153
+ end
154
+
155
+ context "and schema.name is not unique" do
156
+ context "but argument types are unique" do
157
+ before do
158
+ allow(@index).to receive(:procedures).and_return(
159
+ [ @procedure,
160
+ double(:oid => 2,
161
+ :name => q("public", "foo"),
162
+ :type => q("private", "int"),
163
+ :arg_modes => ["in"],
164
+ :arg_names => [],
165
+ :arg_types => []) ])
166
+ end
167
+
168
+ it "specifies schema.name(types)" do
169
+ expect(@index.label(@procedure)).to eq(
170
+ "foo(private.int, private.varchar)")
171
+ end
172
+ end
173
+
174
+ context "and argument types are not unique" do
175
+ context "but argument modes are unique" do
176
+ before do
177
+ allow(@index).to receive(:procedures).and_return(
178
+ [ @procedure,
179
+ double(:oid => 2,
180
+ :name => q("public", "foo"),
181
+ :type => q("private", "int"),
182
+ :arg_modes => ["out", "out"],
183
+ :arg_names => [],
184
+ :arg_types => [q("private", "int"), q("private", "varchar")]) ])
185
+ end
186
+
187
+ it "specifies schema.name(types and modes)" do
188
+ expect(@index.label(@procedure)).to eq(
189
+ "foo(in private.int, in private.varchar)")
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+
197
+ end
198
+
199
+ end
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+
3
+ module Piggly
4
+
5
+ describe Dumper::ReifiedProcedure do
6
+ describe "all" do
7
+ before do
8
+ # stub connection
9
+ end
10
+ end
11
+
12
+ describe "from_hash" do
13
+ it "abbreviates known return types"
14
+ it "leaves alone unknown argument types"
15
+
16
+ it "abbreviates known return types"
17
+ it "leaves alone unknown argument types"
18
+
19
+ it "maps known volatilities"
20
+ it "leaves alone unknown volatilities"
21
+
22
+ it "maps known argument modes"
23
+ it "leaves alone unknown argument modes"
24
+ end
25
+
26
+ describe "store_source" do
27
+ context "when source is already instrumented" do
28
+ it "raises an error"
29
+ end
30
+
31
+ context "when the procedure was identified using the current configuration setting" do
32
+ it "does not attempt to remove any files"
33
+ it "writes to the current location"
34
+ it "has the current identified_using property"
35
+ end
36
+
37
+ context "when the procedure was identified using some other configuration setting" do
38
+ it "removes any old report files"
39
+ it "removes any old trace cache files"
40
+ it "removes the old source cache files"
41
+ it "writes to the current location"
42
+ it "updates the identified_using property"
43
+ end
44
+ end
45
+ end
46
+
47
+ describe Dumper::SkeletonProcedure do
48
+ describe "definition" do
49
+ it "specifies namespace and function name"
50
+ it "specifies source code between dollar-quoted string tags"
51
+
52
+ context "with argument modes" do
53
+ it "specifies argument modes"
54
+ end
55
+
56
+ context "without argument modes" do
57
+ it "doesn't specify argument modes"
58
+ end
59
+
60
+ context "with strict modifier" do
61
+ it "specifies STRICT token"
62
+ end
63
+
64
+ context "without strict modifier" do
65
+ it "doesn't specify STRICT token"
66
+ end
67
+
68
+ context "with security definer modifier" do
69
+ it "specifies SECURITY DEFINER token"
70
+ end
71
+
72
+ context "without security definer modifier" do
73
+ it "doesn't specify SECURITY DEFINER token"
74
+ end
75
+
76
+ context "with set-returning type" do
77
+ it "specifies SETOF token"
78
+ end
79
+
80
+ context "without non set-returning type" do
81
+ it "doesn't specify SETOF token"
82
+ end
83
+
84
+ context "with stable volatility" do
85
+ it "specifies STABLE token"
86
+ end
87
+ end
88
+
89
+ describe "source_path" do
90
+ it "has a .plpgsql extension"
91
+ it "is within the Dumper directory"
92
+ end
93
+
94
+ describe "purge_source" do
95
+ context "when the procedure was identified using the current configuration setting" do
96
+ it "removes any old report files"
97
+ it "removes any old trace cache files"
98
+ it "removes the old source cache files"
99
+ it "removes the current report files"
100
+ it "removes the current trace cache files"
101
+ it "removes the current source cache files"
102
+ it "doesn't attempt to remove any other files"
103
+ end
104
+
105
+ context "when the procedure was identified using some other configuration setting" do
106
+ it "removes the current report files"
107
+ it "removes the current trace cache files"
108
+ it "removes the current source cache files"
109
+ it "doesn't attempt to remove any other files"
110
+ end
111
+ end
112
+
113
+ describe "equality operator"
114
+ end
115
+
116
+ end
@@ -0,0 +1,302 @@
1
+ require 'spec_helper'
2
+
3
+ module Piggly
4
+ describe Parser, "expressions" do
5
+ include GrammarHelper
6
+
7
+ describe "expressionUntilSemiColon" do
8
+ it "does not consume semicolon" do
9
+ # parser stops in front of THEN and dies
10
+ expect{ parse(:expressionUntilSemiColon, 'abc;') }.to raise_error(Piggly::Parser::Failure)
11
+
12
+ node, rest = parse_some(:expressionUntilSemiColon, 'abc; xyz')
13
+ rest.should == '; xyz'
14
+ end
15
+
16
+ it "can be a blank expression" do
17
+ node, rest = parse_some(:expressionUntilSemiColon, ';')
18
+ node.should be_expression
19
+ node.source_text.should == ''
20
+ rest.should == ';'
21
+ end
22
+
23
+ it "can be a comment" do
24
+ node, rest = parse_some(:expressionUntilSemiColon, "/* comment */;")
25
+ node.should be_expression
26
+ node.count{|e| e.comment? }.should == 1
27
+
28
+ node, rest = parse_some(:expressionUntilSemiColon, "-- comment\n;")
29
+ node.should be_expression
30
+ node.count{|e| e.comment? }.should == 1
31
+ end
32
+
33
+ it "can be a string" do
34
+ node, rest = parse_some(:expressionUntilSemiColon, "'string';")
35
+ node.should be_expression
36
+ node.count{|e| e.string? }.should == 1
37
+
38
+ node, rest = parse_some(:expressionUntilSemiColon, "$$ string $$;")
39
+ node.should be_expression
40
+ node.count{|e| e.string? }.should == 1
41
+ end
42
+
43
+ it "can be an arithmetic expression" do
44
+ node, rest = parse_some(:expressionUntilSemiColon, "10 * (3 + x);")
45
+ node.should be_expression
46
+ end
47
+
48
+ it "can be an SQL statement" do
49
+ node, rest = parse_some(:expressionUntilSemiColon, "SELECT id FROM dataset;")
50
+ node.should be_expression
51
+ end
52
+
53
+ it "can be an expression with comments embedded" do
54
+ node, rest = parse_some(:expressionUntilSemiColon, <<-SQL)
55
+ SELECT id -- primary key ;
56
+ FROM "dataset" /* ; */ -- previous comments shouldn't terminate expression
57
+ WHERE value IS /*NOT*/ NULL;
58
+ SQL
59
+ node.should be_expression
60
+ node.count{|e| e.comment? }.should == 4
61
+ end
62
+
63
+ it "can be an expression with strings and comments embedded" do
64
+ node, rest = parse_some(:expressionUntilSemiColon, <<-SQL)
65
+ SELECT id -- 1. upcoming single quote doesn't matter
66
+ FROM dataset /* 2. this one's no problem either */
67
+ WHERE value LIKE '/* comment within a string! shouldn''t parse a comment */'
68
+ AND length(value) > 10 -- 3. this comment in tail doesn't contain any 'string's
69
+ /* 4. farewell comment in tail */;
70
+ SQL
71
+ node.should be_expression
72
+ node.count{|e| e.comment? }.should == 4
73
+ node.count{|e| e.string? }.should == 1
74
+ end
75
+
76
+ it "can be an expression with strings embedded" do
77
+ node, rest = parse_some(:expressionUntilSemiColon, <<-SQL)
78
+ SELECT id, created_at
79
+ FROM "dataset"
80
+ WHERE value IS NOT NULL
81
+ AND value <> '; this should not terminate expression'
82
+ AND created_at = '2001-01-01';
83
+ SQL
84
+ node.should be_expression
85
+ node.count{|e| e.string? }.should == 2
86
+ end
87
+
88
+ it "combines trailing whitespace into 'tail' node" do
89
+ node, rest = parse_some(:expressionUntilSemiColon, "a := x + y \t;")
90
+ node.should be_expression
91
+ node.tail.source_text.should == " \t"
92
+ end
93
+
94
+ it "combines trailing comments into 'tail' node" do
95
+ node, rest = parse_some(:expressionUntilSemiColon, "a := x + y /* note -- comment */;")
96
+ node.should be_expression
97
+ node.tail.source_text.should == ' /* note -- comment */'
98
+
99
+ node, rest = parse_some(:expressionUntilSemiColon, <<-SQL)
100
+ SELECT id -- 1. upcoming single quote doesn't matter
101
+ FROM dataset /* 2. this one's no problem either */
102
+ WHERE value LIKE '/* comment within a string! shouldn''t parse a comment */'
103
+ AND length(value) > 10 -- 3. this comment in tail doesn't contain any 'string's
104
+ /* 4. farewell comment in tail */;
105
+ SQL
106
+ node.tail.count{|e| e.comment? }.should == 2
107
+ end
108
+ end
109
+
110
+ describe "expressionUntilThen" do
111
+ it "does not consume THEN token" do
112
+ # parser stops in front of THEN and dies
113
+ expect{ parse(:expressionUntilThen, 'abc THEN') }.to raise_error(Piggly::Parser::Failure)
114
+
115
+ node, rest = parse_some(:expressionUntilThen, 'abc THEN xyz')
116
+ rest.should == 'THEN xyz'
117
+ end
118
+
119
+ it "cannot be a blank expression" do
120
+ expect{ parse_some(:expressionUntilThen, ' THEN') }.to raise_error(Piggly::Parser::Failure)
121
+ end
122
+
123
+ it "cannot be a comment" do
124
+ expect{ parse_some(:expressionUntilThen, "/* comment */ THEN") }.to raise_error(Piggly::Parser::Failure)
125
+ expect{ parse_some(:expressionUntilThen, "-- comment\n THEN") }.to raise_error(Piggly::Parser::Failure)
126
+ end
127
+
128
+ it "can be a string" do
129
+ node, rest = parse_some(:expressionUntilThen, "'string' THEN")
130
+ node.should be_expression
131
+ node.count{|e| e.string? }.should == 1
132
+
133
+ node, rest = parse_some(:expressionUntilThen, "$$ string $$ THEN")
134
+ node.should be_expression
135
+ node.count{|e| e.string? }.should == 1
136
+ end
137
+
138
+ it "can be an arithmetic expression" do
139
+ node, rest = parse_some(:expressionUntilThen, "10 * (3 + x) THEN")
140
+ node.should be_expression
141
+ end
142
+
143
+ it "can be an SQL statement" do
144
+ node, rest = parse_some(:expressionUntilThen, "SELECT id FROM dataset THEN")
145
+ node.should be_expression
146
+ end
147
+
148
+ it "can be an expression with comments embedded" do
149
+ node, rest = parse_some(:expressionUntilThen, <<-SQL)
150
+ SELECT id -- primary key THEN
151
+ FROM "dataset" /* THEN */ -- previous comments shouldn't terminate expression
152
+ WHERE value IS /*NOT*/ NULL THEN
153
+ SQL
154
+ node.should be_expression
155
+ node.count{|e| e.comment? }.should == 4
156
+ end
157
+
158
+ it "can be an expression with strings and comments embedded" do
159
+ node, rest = parse_some(:expressionUntilThen, <<-SQL)
160
+ SELECT id -- 1. upcoming single quote doesn't matter
161
+ FROM dataset /* 2. this one's no problem either */
162
+ WHERE value LIKE '/* comment within a string! shouldn''t parse a comment */'
163
+ AND length(value) > 10 -- 3. this comment in tail doesn't contain any 'string's
164
+ /* 4. farewell comment in tail */ THEN
165
+ SQL
166
+ node.should be_expression
167
+ node.count{|e| e.comment? }.should == 4
168
+ node.count{|e| e.string? }.should == 1
169
+ end
170
+
171
+ it "can be an expression with strings embedded" do
172
+ node, rest = parse_some(:expressionUntilThen, <<-SQL)
173
+ SELECT id, created_at
174
+ FROM "dataset"
175
+ WHERE value IS NOT NULL
176
+ AND value <> ' THEN this should not terminate expression'
177
+ AND created_at = '2001-01-01' THEN
178
+ SQL
179
+ node.should be_expression
180
+ node.count{|e| e.string? }.should == 2
181
+ end
182
+
183
+ it "combines trailing whitespace into 'tail' node" do
184
+ node, rest = parse_some(:expressionUntilThen, "a := x + y \tTHEN")
185
+ node.should be_expression
186
+ node.tail.source_text.should == " \t"
187
+ end
188
+
189
+ it "combines trailing comments into 'tail' node" do
190
+ node, rest = parse_some(:expressionUntilThen, "a := x + y /* note -- comment */THEN")
191
+ node.should be_expression
192
+ node.tail.source_text.should == ' /* note -- comment */'
193
+
194
+ node, rest = parse_some(:expressionUntilThen, <<-SQL)
195
+ SELECT id -- 1. upcoming single quote doesn't matter
196
+ FROM dataset /* 2. this one's no problem either */
197
+ WHERE value LIKE '/* comment within a string! shouldn''t parse a comment */'
198
+ AND length(value) > 10 -- 3. this comment in tail doesn't contain any 'string's
199
+ /* 4. farewell comment in tail */THEN
200
+ SQL
201
+ node.tail.count{|e| e.comment? }.should == 2
202
+ end
203
+ end
204
+
205
+ describe "expressionUntilLoop" do
206
+ it "does not consume LOOP token" do
207
+ # parser stops in front of LOOP and dies
208
+ expect{ parse(:expressionUntilLoop, 'abc LOOP') }.to raise_error(Piggly::Parser::Failure)
209
+
210
+ node, rest = parse_some(:expressionUntilLoop, 'abc LOOP xyz')
211
+ rest.should == 'LOOP xyz'
212
+ end
213
+
214
+ it "cannot be a blank expression" do
215
+ expect{ parse_some(:expressionUntilLoop, ' LOOP') }.to raise_error(Piggly::Parser::Failure)
216
+ end
217
+
218
+ it "cannot be a comment" do
219
+ expect{ parse_some(:expressionUntilLoop, "/* comment */ LOOP") }.to raise_error(Piggly::Parser::Failure)
220
+ expect{ parse_some(:expressionUntilLoop, "-- comment\n LOOP") }.to raise_error(Piggly::Parser::Failure)
221
+ end
222
+
223
+ it "can be a string" do
224
+ node, rest = parse_some(:expressionUntilLoop, "'string' LOOP")
225
+ node.should be_expression
226
+ node.count{|e| e.string? }.should == 1
227
+
228
+ node, rest = parse_some(:expressionUntilLoop, "$$ string $$ LOOP")
229
+ node.should be_expression
230
+ node.count{|e| e.string? }.should == 1
231
+ end
232
+
233
+ it "can be an arithmetic expression" do
234
+ node, rest = parse_some(:expressionUntilLoop, "10 * (3 + x) LOOP")
235
+ node.should be_expression
236
+ end
237
+
238
+ it "can be an SQL statement" do
239
+ node, rest = parse_some(:expressionUntilLoop, "SELECT id FROM dataset LOOP")
240
+ node.should be_expression
241
+ end
242
+
243
+ it "can be an expression with comments embedded" do
244
+ node, rest = parse_some(:expressionUntilLoop, <<-SQL)
245
+ SELECT id -- primary key LOOP
246
+ FROM "dataset" /* LOOP */ -- previous comments shouldn't terminate expression
247
+ WHERE value IS /*NOT*/ NULL LOOP
248
+ SQL
249
+ node.should be_expression
250
+ node.count{|e| e.comment? }.should == 4
251
+ end
252
+
253
+ it "can be an expression with strings and comments embedded" do
254
+ node, rest = parse_some(:expressionUntilLoop, <<-SQL)
255
+ SELECT id -- 1. upcoming single quote doesn't matter
256
+ FROM dataset /* 2. this one's no problem either */
257
+ WHERE value LIKE '/* comment within a string! shouldn''t parse a comment */'
258
+ AND length(value) > 10 -- 3. this comment in tail doesn't contain any 'string's
259
+ /* 4. farewell comment in tail */ LOOP
260
+ SQL
261
+ node.should be_expression
262
+ node.count{|e| e.comment? }.should == 4
263
+ node.count{|e| e.string? }.should == 1
264
+ end
265
+
266
+ it "can be an expression with strings embedded" do
267
+ node, rest = parse_some(:expressionUntilLoop, <<-SQL)
268
+ SELECT id, created_at
269
+ FROM "dataset"
270
+ WHERE value IS NOT NULL
271
+ AND value <> ' LOOP this should not terminate expression'
272
+ AND created_at = '2001-01-01' LOOP
273
+ SQL
274
+ node.should be_expression
275
+ node.count{|e| e.string? }.should == 2
276
+ end
277
+
278
+ it "combines trailing whitespace into 'tail' node" do
279
+ node, rest = parse_some(:expressionUntilLoop, "a := x + y \tLOOP")
280
+ node.should be_expression
281
+ node.tail.source_text.should == " \t"
282
+ end
283
+
284
+ it "combines trailing comments into 'tail' node" do
285
+ node, rest = parse_some(:expressionUntilLoop, "a := x + y /* note -- comment */LOOP")
286
+ node.should be_expression
287
+ node.tail.source_text.should == ' /* note -- comment */'
288
+
289
+ node, rest = parse_some(:expressionUntilLoop, <<-SQL)
290
+ SELECT id -- 1. upcoming single quote doesn't matter
291
+ FROM dataset /* 2. this one's no problem either */
292
+ WHERE value LIKE '/* comment within a string! shouldn''t parse a comment */'
293
+ AND length(value) > 10 -- 3. this comment in tail doesn't contain any 'string's
294
+ /* 4. farewell comment in tail */LOOP
295
+ SQL
296
+ node.tail.count{|e| e.comment? }.should == 2
297
+ end
298
+ end
299
+
300
+ end
301
+
302
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ module Piggly
4
+
5
+ describe Parser, "statements" do
6
+ include GrammarHelper
7
+
8
+ describe "assignment statements" do
9
+ it "parse successfully" do
10
+ node = parse(:statement, "a := 10;")
11
+ node.should be_statement
12
+ node.count{|e| e.assignment? }.should == 1
13
+
14
+ node.find{|e| e.named? :lval }.should be_a(Parser::Nodes::Assignable)
15
+ node.find{|e| e.named? :rval }.should be_expression
16
+ end
17
+
18
+ it "must end with a semicolon" do
19
+ expect { parse_some(:statement, 'a := 10') }.to raise_error(Piggly::Parser::Failure)
20
+ expect { parse(:statement, 'a := 10') }.to raise_error(Piggly::Parser::Failure)
21
+ end
22
+
23
+ it "can use := or =" do
24
+ a = parse(:statement, "a := 10;")
25
+ a.should be_statement
26
+ a.count{|e| e.assignment? }.should == 1
27
+
28
+ b = parse(:statement, "a = 10;")
29
+ b.should be_statement
30
+ b.count{|e| e.assignment? }.should == 1
31
+ end
32
+
33
+ it "can assign strings" do
34
+ node = parse(:statement, "a := 'string';")
35
+ rval = node.find{|e| e.named? :rval }
36
+ rval.count{|e| e.string? }.should == 1
37
+ end
38
+
39
+ it "can assign value expressions containing comments" do
40
+ ['a := /* comment */ 100;',
41
+ 'a := 100 /* comment */;',
42
+ 'a := 10 /* comment */ + 10;'].test_each do |s|
43
+ node = parse(:statement, s)
44
+ rval = node.find{|e| e.named? :rval }
45
+ rval.count{|e| e.comment? }.should == 1
46
+ end
47
+ end
48
+
49
+ it "can assign value expressions containing strings" do
50
+ node = parse(:statement, "a := 'Hello,' || space || 'world';")
51
+ rval = node.find{|e| e.named? :rval }
52
+ rval.count{|e| e.string? }.should == 2
53
+ end
54
+
55
+ it "can assign value expressions containing comments and strings" do
56
+ node = parse(:statement, <<-SQL.strip)
57
+ a := (SELECT fk, count(*), 'not a statement;'
58
+ FROM dataset
59
+ WHERE id > 100 /* filter out 'reserved' range */
60
+ AND id < 900 -- shouldn't be a string
61
+ OR value <> 'alpha /* no comment */;'
62
+ GROUP BY /* pk ; 'abc' */ fk);
63
+ SQL
64
+ rval = node.find{|e| e.named? :rval }
65
+ rval.count{|e| e.string? }.should == 2
66
+ end
67
+ end
68
+
69
+ end
70
+ end