each_sql 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +18 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +52 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/each_sql/each_sql.rb +160 -0
- data/lib/each_sql.rb +92 -0
- data/test/helper.rb +17 -0
- data/test/postgres.sql +128 -0
- data/test/test_each_sql.rb +139 -0
- metadata +95 -0
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "bundler", "~> 1.0.0"
|
10
|
+
gem "jeweler", "~> 1.6.2"
|
11
|
+
gem "rcov", ">= 0"
|
12
|
+
end
|
data/Gemfile.lock
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Junegunn Choi
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
= each_sql
|
2
|
+
|
3
|
+
Enumerate each SQL statement in the given SQL script.
|
4
|
+
|
5
|
+
== Installation
|
6
|
+
gem install 'each_sql'
|
7
|
+
|
8
|
+
== Example
|
9
|
+
=== Basic
|
10
|
+
require 'each_sql'
|
11
|
+
|
12
|
+
EachSQL(sql_script).each do |sql|
|
13
|
+
puts sql
|
14
|
+
end
|
15
|
+
|
16
|
+
sqls = EachSQL(sql_script).to_a
|
17
|
+
|
18
|
+
=== For scripts containing vendor-specific syntax
|
19
|
+
# For MySQL script
|
20
|
+
EachSQL(mysql_script, :mysql).each do |sql|
|
21
|
+
# ...
|
22
|
+
end
|
23
|
+
|
24
|
+
# For Oracle PL/SQL scripts
|
25
|
+
EachSQL(plsql_script, :oracle).each do |sql|
|
26
|
+
# ...
|
27
|
+
end
|
28
|
+
|
29
|
+
== TODO
|
30
|
+
- More/better tests.
|
31
|
+
- pgplsql support.
|
32
|
+
|
33
|
+
== Warning
|
34
|
+
Should be good enough for regular SQLs, but it is not guaranteed to parse stored procedures correctly.
|
35
|
+
I thought this would be simple when I first started, but actually it wasn't, and the code now is a mess.
|
36
|
+
Use it at your own risk.
|
37
|
+
|
38
|
+
== Contributing to each_sql
|
39
|
+
|
40
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
41
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
42
|
+
* Fork the project
|
43
|
+
* Start a feature/bugfix branch
|
44
|
+
* Commit and push until you are happy with your contribution
|
45
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
46
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
47
|
+
|
48
|
+
== Copyright
|
49
|
+
|
50
|
+
Copyright (c) 2011 Junegunn Choi. See LICENSE.txt for
|
51
|
+
further details.
|
52
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "each_sql"
|
18
|
+
gem.homepage = "http://github.com/junegunn/each_sql"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Enumerate through each SQL statement in SQL scripts.}
|
21
|
+
gem.description = %Q{Enumerate through each SQL statement in SQL scripts.}
|
22
|
+
gem.email = "junegunn.c@gmail.com"
|
23
|
+
gem.authors = ["Junegunn Choi"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'rcov/rcovtask'
|
36
|
+
Rcov::RcovTask.new do |test|
|
37
|
+
test.libs << 'test'
|
38
|
+
test.pattern = 'test/**/test_*.rb'
|
39
|
+
test.verbose = true
|
40
|
+
test.rcov_opts << '--exclude "gems/*"'
|
41
|
+
end
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "each_sql #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# Junegunn Choi (junegunn.c@gmail.com)
|
3
|
+
|
4
|
+
# Enumerable EachSQL object.
|
5
|
+
class EachSQL
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize input, options
|
9
|
+
raise NotImplementedError.new if options.nil?
|
10
|
+
# immutables
|
11
|
+
@org_input = input
|
12
|
+
@options = options
|
13
|
+
@blocks = @options[:blocks]
|
14
|
+
@nblocks = @options[:nesting_blocks]
|
15
|
+
@all_blocks = @blocks.merge @nblocks
|
16
|
+
end
|
17
|
+
|
18
|
+
def each
|
19
|
+
@input = @org_input
|
20
|
+
return nil if @input.nil? || @input.empty?
|
21
|
+
|
22
|
+
@delimiter = @options[:delimiter]
|
23
|
+
|
24
|
+
reset_cursor
|
25
|
+
while @input
|
26
|
+
# First look for next delimiter, this is to reduce the search space for blocks.
|
27
|
+
extend_scope
|
28
|
+
|
29
|
+
# We're done. Finished. Period. Out!
|
30
|
+
break if scope.empty?
|
31
|
+
|
32
|
+
# Extract a statement
|
33
|
+
statement = extract_statement
|
34
|
+
|
35
|
+
# When a non-empty statement is found
|
36
|
+
statement = @options[:strip_delimiter].call self, statement if @options[:strip_delimiter]
|
37
|
+
if statement.length > 0
|
38
|
+
# Apply replacements
|
39
|
+
@options[:replace].each do |k, v|
|
40
|
+
statement.gsub!(k, v)
|
41
|
+
end
|
42
|
+
statement.strip!
|
43
|
+
|
44
|
+
# Process callbacks
|
45
|
+
@options[:callbacks].each do |pattern, callback|
|
46
|
+
md = statement.match pattern
|
47
|
+
callback.call self, statement, md if md
|
48
|
+
end
|
49
|
+
|
50
|
+
# Ignore
|
51
|
+
if (@options[:ignore] || []).all? { |ipat| statement !~ ipat }
|
52
|
+
yield statement
|
53
|
+
@prev_statement = statement
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
attr_accessor :delimiter, :delimiter_string
|
61
|
+
private
|
62
|
+
|
63
|
+
def extract_statement
|
64
|
+
while process_next_block != :not_found
|
65
|
+
end
|
66
|
+
|
67
|
+
ret = scope.strip
|
68
|
+
@input = @input[@next_head..-1]
|
69
|
+
reset_cursor
|
70
|
+
return ret
|
71
|
+
end
|
72
|
+
|
73
|
+
def reset_cursor
|
74
|
+
@cur = @from = @to = 0
|
75
|
+
end
|
76
|
+
|
77
|
+
def scope
|
78
|
+
@to ? @input[0, @to] : @input
|
79
|
+
end
|
80
|
+
|
81
|
+
def extend_scope
|
82
|
+
md = @input.match @delimiter, @to
|
83
|
+
|
84
|
+
if md
|
85
|
+
@to = md.begin(0) + md[0].length
|
86
|
+
@next_head = @to
|
87
|
+
else
|
88
|
+
@to = @input.length
|
89
|
+
@next_head = @input.length
|
90
|
+
end
|
91
|
+
#puts "Extended: #{scope.inspect} #{@input[@next_head..-1]}"
|
92
|
+
end
|
93
|
+
|
94
|
+
def process_next_block expect = nil
|
95
|
+
# Look for the closest block
|
96
|
+
block_start, opener_length, opener, closer = @all_blocks.map { |opener, closer|
|
97
|
+
closer = closer[:closer] if closer.is_a? Hash
|
98
|
+
md = scope.match(opener, @cur)
|
99
|
+
[md && md.begin(0), md && md[0].length, opener, closer]
|
100
|
+
}.reject { |e| e.first.nil? }.min_by(&:first)
|
101
|
+
|
102
|
+
# p [scope, scope[@cur..-1], expect, scope.index(expect, @cur), block_start] if expect
|
103
|
+
# We found a block, but after the end of the nesting block
|
104
|
+
if expect &&
|
105
|
+
(prev_end = scope.index(expect, @cur)) &&
|
106
|
+
(block_start.nil? || prev_end <= block_start)
|
107
|
+
skip_through_block expect, prev_end == block_start
|
108
|
+
return :end_nest
|
109
|
+
end
|
110
|
+
|
111
|
+
# If no block in this scope
|
112
|
+
return :not_found if block_start.nil?
|
113
|
+
|
114
|
+
# We found a block. Look for the end of it
|
115
|
+
@cur = block_start + opener_length
|
116
|
+
|
117
|
+
# If nesting block, we go deeper
|
118
|
+
if @nblocks.keys.include? opener
|
119
|
+
@prev_delimiter = @delimiter
|
120
|
+
if @nblocks[opener].is_a? Hash
|
121
|
+
@delimiter = @nblocks[opener][:delimiter] || @delimiter
|
122
|
+
end
|
123
|
+
while true
|
124
|
+
ret = process_next_block(closer)
|
125
|
+
|
126
|
+
break if ret == :end_nest
|
127
|
+
extend_scope if ret == :not_found
|
128
|
+
throw_exception(closer) if scope.length == @input.length
|
129
|
+
end
|
130
|
+
@delimiter = @prev_delimiter
|
131
|
+
|
132
|
+
# If non-nesting block, just skip through it
|
133
|
+
else
|
134
|
+
skip_through_block closer
|
135
|
+
end
|
136
|
+
|
137
|
+
return :continue
|
138
|
+
end
|
139
|
+
|
140
|
+
def skip_through_block closer, rewind_delimiter = false
|
141
|
+
md = @input.match closer, @cur
|
142
|
+
block_end = md && md.begin(0)
|
143
|
+
|
144
|
+
throw_exception(closer) if block_end.nil?
|
145
|
+
|
146
|
+
@cur = block_end + (rewind_delimiter ? 0 : md[0].length)
|
147
|
+
while @cur > scope.length
|
148
|
+
extend_scope
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def throw_exception closer
|
153
|
+
raise ArgumentError.new(
|
154
|
+
"Unclosed block: was expecting #{closer.inspect} " +
|
155
|
+
"while processing #{$/ + scope.inspect}" +
|
156
|
+
(@prev_statement ?
|
157
|
+
" after #{@prev_statement.inspect}" : ""))
|
158
|
+
end
|
159
|
+
end#EachSQL
|
160
|
+
|
data/lib/each_sql.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# Junegunn Choi (junegunn.c@gmail.com)
|
3
|
+
|
4
|
+
require 'each_sql/each_sql'
|
5
|
+
|
6
|
+
# Shortcut method for creating a Enumerable EachSQL object for the given input.
|
7
|
+
# @param[String] input Input script.
|
8
|
+
# @param[Symbol] The type of the input SQL script. :default, :mysql, and :oracle (or :plsql)
|
9
|
+
# @return[EachSQL] Enumerable
|
10
|
+
def EachSQL input, type = :default
|
11
|
+
EachSQL.new(input, EachSQL::Defaults[type])
|
12
|
+
end
|
13
|
+
|
14
|
+
class EachSQL
|
15
|
+
# EachSQL::Default Hash is a set of pre-defined parsing rules
|
16
|
+
# - :default: Default parsing rules for vendor-independent SQL scripts
|
17
|
+
# - :mysql: Parsing rules for MySQL scripts. Understands `delimiter' statements.
|
18
|
+
# - :oracle: Parsing rules for Oracle scripts. Removes trailing slashes after begin-end blocks.
|
19
|
+
Defaults = {
|
20
|
+
:default => {
|
21
|
+
:delimiter => /;+/,
|
22
|
+
:blocks => {
|
23
|
+
/'/ => /'/,
|
24
|
+
/\/\*[^+]/ => /\*\//,
|
25
|
+
/--+/ => $/,
|
26
|
+
},
|
27
|
+
:nesting_blocks => {
|
28
|
+
/\bdeclare\b/i => /\bbegin\b/i,
|
29
|
+
/\bbegin\b/i => /\bend\b/i
|
30
|
+
},
|
31
|
+
:callbacks => {},
|
32
|
+
:ignore => [],
|
33
|
+
:replace => {},
|
34
|
+
# Let's assume we don't change delimiters within usual sql scripts
|
35
|
+
:strip_delimiter => lambda { |obj, stmt| stmt.chomp ';' }
|
36
|
+
},
|
37
|
+
|
38
|
+
:mysql => {
|
39
|
+
:delimiter => /;+|delimiter\s+\S+/i,
|
40
|
+
:blocks => {
|
41
|
+
/'/ => /'/,
|
42
|
+
/\/\*[^+]/ => /\*\//,
|
43
|
+
/--+/ => $/,
|
44
|
+
},
|
45
|
+
:nesting_blocks => {
|
46
|
+
/\bbegin\b/i => /\bend\b/i
|
47
|
+
},
|
48
|
+
# We need to change delimiter on `delimiter' command
|
49
|
+
:callbacks => {
|
50
|
+
/^\s*delimiter\s+(\S+)/i => lambda { |obj, stmt, md|
|
51
|
+
new_delimiter = Regexp.new(Regexp.escape md[1])
|
52
|
+
obj.delimiter = /#{new_delimiter}+|delimiter\s+\S+/i
|
53
|
+
obj.delimiter_string = md[1]
|
54
|
+
}
|
55
|
+
},
|
56
|
+
:ignore => [
|
57
|
+
/^delimiter\s+\S+$/i
|
58
|
+
],
|
59
|
+
:replace => {},
|
60
|
+
:strip_delimiter => lambda { |obj, stmt|
|
61
|
+
stmt.chomp(obj.delimiter_string || ';')
|
62
|
+
}
|
63
|
+
},
|
64
|
+
|
65
|
+
:oracle => {
|
66
|
+
:delimiter => /;+/,
|
67
|
+
:blocks => {
|
68
|
+
/'/ => /'/,
|
69
|
+
/\/\*[^+]/ => /\*\//,
|
70
|
+
/--+/ => $/,
|
71
|
+
},
|
72
|
+
:nesting_blocks => {
|
73
|
+
/\bbegin\b/i => /\bend\b/i,
|
74
|
+
/\bcreate[^;]*\b(procedure|function|trigger|package)\b/im => {
|
75
|
+
:closer => %r{;\s*/}m,
|
76
|
+
:delimiter => /;\s*\//
|
77
|
+
}
|
78
|
+
},
|
79
|
+
:callbacks => {},
|
80
|
+
:ignore => [],
|
81
|
+
:replace => {},
|
82
|
+
:strip_delimiter => lambda { |obj, stmt| obj
|
83
|
+
stmt.chomp( stmt =~ /;\s*\// ? '/' : ';' )
|
84
|
+
}
|
85
|
+
}
|
86
|
+
}
|
87
|
+
Defaults[:plsql] = Defaults[:oracle] # alias
|
88
|
+
|
89
|
+
# Freeze the Hash
|
90
|
+
Defaults.freeze
|
91
|
+
end
|
92
|
+
|
data/test/helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
|
12
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
require 'each_sql'
|
15
|
+
|
16
|
+
class Test::Unit::TestCase
|
17
|
+
end
|
data/test/postgres.sql
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--
|
2
|
+
-- Main tables - time dimension and sales fact.
|
3
|
+
--
|
4
|
+
CREATE TABLE time_dimension (
|
5
|
+
time_key integer NOT NULL,
|
6
|
+
day_of_week integer NOT NULL,
|
7
|
+
day_of_month integer NOT NULL,
|
8
|
+
month integer NOT NULL,
|
9
|
+
quarter integer NOT NULL,
|
10
|
+
year integer NOT NULL
|
11
|
+
);
|
12
|
+
CREATE UNIQUE INDEX time_dimension_key ON time_dimension(time_key);
|
13
|
+
|
14
|
+
CREATE TABLE sales_fact (
|
15
|
+
time_key integer NOT NULL,
|
16
|
+
product_key integer NOT NULL,
|
17
|
+
store_key integer NOT NULL,
|
18
|
+
amount_sold numeric(12,2) NOT NULL,
|
19
|
+
units_sold integer NOT NULL,
|
20
|
+
amount_cost numeric(12,2) NOT NULL
|
21
|
+
);
|
22
|
+
CREATE INDEX sales_fact_time ON sales_fact(time_key);
|
23
|
+
|
24
|
+
--
|
25
|
+
-- Summary table - sales by time.
|
26
|
+
--
|
27
|
+
CREATE TABLE sales_summary_bytime (
|
28
|
+
time_key integer NOT NULL,
|
29
|
+
amount_sold numeric(15,2) NOT NULL,
|
30
|
+
units_sold numeric(12) NOT NULL,
|
31
|
+
amount_cost numeric(15,2) NOT NULL
|
32
|
+
);
|
33
|
+
CREATE UNIQUE INDEX sales_summary_bytime_key ON sales_summary_bytime(time_key);
|
34
|
+
|
35
|
+
--
|
36
|
+
-- Function and trigger to amend summarized column(s) on UPDATE, INSERT, DELETE.
|
37
|
+
--
|
38
|
+
CREATE OR REPLACE FUNCTION maint_sales_summary_bytime() RETURNS TRIGGER AS $maint_sales_summary_bytime$
|
39
|
+
DECLARE
|
40
|
+
delta_time_key integer;
|
41
|
+
delta_amount_sold numeric(15,2);
|
42
|
+
delta_units_sold numeric(12);
|
43
|
+
delta_amount_cost numeric(15,2);
|
44
|
+
BEGIN
|
45
|
+
|
46
|
+
-- Work out the increment/decrement amount(s).
|
47
|
+
IF (TG_OP = 'DELETE') THEN
|
48
|
+
|
49
|
+
delta_time_key = OLD.time_key;
|
50
|
+
delta_amount_sold = -1 * OLD.amount_sold;
|
51
|
+
delta_units_sold = -1 * OLD.units_sold;
|
52
|
+
delta_amount_cost = -1 * OLD.amount_cost;
|
53
|
+
|
54
|
+
ELSIF (TG_OP = 'UPDATE') THEN
|
55
|
+
|
56
|
+
-- forbid updates that change the time_key -
|
57
|
+
-- (probably not too onerous, as DELETE + INSERT is how most
|
58
|
+
-- changes will be made).
|
59
|
+
IF ( OLD.time_key != NEW.time_key) THEN
|
60
|
+
RAISE EXCEPTION 'Update of time_key : % -> % not allowed', OLD.time_key, NEW.time_key;
|
61
|
+
END IF;
|
62
|
+
|
63
|
+
delta_time_key = OLD.time_key;
|
64
|
+
delta_amount_sold = NEW.amount_sold - OLD.amount_sold;
|
65
|
+
delta_units_sold = NEW.units_sold - OLD.units_sold;
|
66
|
+
delta_amount_cost = NEW.amount_cost - OLD.amount_cost;
|
67
|
+
|
68
|
+
ELSIF (TG_OP = 'INSERT') THEN
|
69
|
+
|
70
|
+
delta_time_key = NEW.time_key;
|
71
|
+
delta_amount_sold = NEW.amount_sold;
|
72
|
+
delta_units_sold = NEW.units_sold;
|
73
|
+
delta_amount_cost = NEW.amount_cost;
|
74
|
+
|
75
|
+
END IF;
|
76
|
+
|
77
|
+
|
78
|
+
-- Insert or update the summary row with the new values.
|
79
|
+
<<insert_update>>
|
80
|
+
LOOP
|
81
|
+
UPDATE sales_summary_bytime
|
82
|
+
SET amount_sold = amount_sold + delta_amount_sold,
|
83
|
+
units_sold = units_sold + delta_units_sold,
|
84
|
+
amount_cost = amount_cost + delta_amount_cost
|
85
|
+
WHERE time_key = delta_time_key;
|
86
|
+
|
87
|
+
EXIT insert_update WHEN found;
|
88
|
+
|
89
|
+
BEGIN
|
90
|
+
INSERT INTO sales_summary_bytime (
|
91
|
+
time_key,
|
92
|
+
amount_sold,
|
93
|
+
units_sold,
|
94
|
+
amount_cost)
|
95
|
+
VALUES (
|
96
|
+
delta_time_key,
|
97
|
+
delta_amount_sold,
|
98
|
+
delta_units_sold,
|
99
|
+
delta_amount_cost
|
100
|
+
);
|
101
|
+
|
102
|
+
EXIT insert_update;
|
103
|
+
|
104
|
+
EXCEPTION
|
105
|
+
WHEN UNIQUE_VIOLATION THEN
|
106
|
+
-- do nothing
|
107
|
+
END;
|
108
|
+
END LOOP insert_update;
|
109
|
+
|
110
|
+
RETURN NULL;
|
111
|
+
|
112
|
+
END;
|
113
|
+
$maint_sales_summary_bytime$ LANGUAGE plpgsql;
|
114
|
+
|
115
|
+
CREATE TRIGGER maint_sales_summary_bytime
|
116
|
+
AFTER INSERT OR UPDATE OR DELETE ON sales_fact
|
117
|
+
FOR EACH ROW EXECUTE PROCEDURE maint_sales_summary_bytime();
|
118
|
+
|
119
|
+
INSERT INTO sales_fact VALUES(1,1,1,10,3,15);
|
120
|
+
INSERT INTO sales_fact VALUES(1,2,1,20,5,35);
|
121
|
+
INSERT INTO sales_fact VALUES(2,2,1,40,15,135);
|
122
|
+
INSERT INTO sales_fact VALUES(2,3,1,10,1,13);
|
123
|
+
SELECT * FROM sales_summary_bytime;
|
124
|
+
DELETE FROM sales_fact WHERE product_key = 1;
|
125
|
+
SELECT * FROM sales_summary_bytime;
|
126
|
+
UPDATE sales_fact SET units_sold = units_sold * 2;
|
127
|
+
SELECT * FROM sales_summary_bytime;
|
128
|
+
|
@@ -0,0 +1,139 @@
|
|
1
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
2
|
+
require 'helper'
|
3
|
+
|
4
|
+
class TestEachSql < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@sql = [
|
7
|
+
"select * from a",
|
8
|
+
"select
|
9
|
+
*
|
10
|
+
from
|
11
|
+
b",
|
12
|
+
"select 'abc', 'abc;', 'abc''', 'abc/*', 'abc--' from c",
|
13
|
+
|
14
|
+
"select
|
15
|
+
/*+ help */ *
|
16
|
+
from
|
17
|
+
d",
|
18
|
+
"select * from /* block comment ; */ e",
|
19
|
+
"select *
|
20
|
+
from -- line comment ; /* ;; */
|
21
|
+
f",
|
22
|
+
"-------------- begin-end block;
|
23
|
+
begin
|
24
|
+
-- begin-end block;
|
25
|
+
-- line comment
|
26
|
+
-- line comment
|
27
|
+
-- line comment
|
28
|
+
begin
|
29
|
+
null;
|
30
|
+
begin
|
31
|
+
null;
|
32
|
+
end;
|
33
|
+
end;
|
34
|
+
-- end
|
35
|
+
/* end */
|
36
|
+
end",
|
37
|
+
"-------------- begin-end block;
|
38
|
+
declare
|
39
|
+
/* end; */
|
40
|
+
/* begin */
|
41
|
+
null;
|
42
|
+
null;
|
43
|
+
null;
|
44
|
+
begin
|
45
|
+
/* end */
|
46
|
+
end",
|
47
|
+
"-------------- begin-end block;
|
48
|
+
declare
|
49
|
+
/* end; */
|
50
|
+
/* begin */
|
51
|
+
null;
|
52
|
+
null;
|
53
|
+
null;
|
54
|
+
begin
|
55
|
+
-- begin-end block;
|
56
|
+
-- line comment
|
57
|
+
-- line comment
|
58
|
+
-- line comment
|
59
|
+
begin
|
60
|
+
null;
|
61
|
+
begin
|
62
|
+
null;
|
63
|
+
end;
|
64
|
+
end;
|
65
|
+
-- end
|
66
|
+
/* end */
|
67
|
+
end",
|
68
|
+
"select * from dual",
|
69
|
+
"select * from dual"]
|
70
|
+
|
71
|
+
@oracle = "
|
72
|
+
select * from dual;
|
73
|
+
Create or replace Procedure tmmp(p1 number, p2 number) as
|
74
|
+
str number(8, 2) := 1 / 4;
|
75
|
+
begin
|
76
|
+
begin
|
77
|
+
1 / 4;
|
78
|
+
null;
|
79
|
+
end;
|
80
|
+
exception
|
81
|
+
when others then
|
82
|
+
raise;
|
83
|
+
end;
|
84
|
+
/
|
85
|
+
select * from dual;"
|
86
|
+
|
87
|
+
@mysql = "
|
88
|
+
delimiter //
|
89
|
+
drop procedure if exists proc //
|
90
|
+
create procedure proc(p1 int, p2 int)
|
91
|
+
begin
|
92
|
+
null;
|
93
|
+
|
94
|
+
end //
|
95
|
+
delimiter ;
|
96
|
+
|
97
|
+
delimiter $$
|
98
|
+
drop procedure if exists proc2 $$
|
99
|
+
create procedure proc(p1 int, p2 int)
|
100
|
+
begin
|
101
|
+
null;
|
102
|
+
|
103
|
+
end $$
|
104
|
+
delimiter ;
|
105
|
+
|
106
|
+
select * from dual;"
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_sql
|
110
|
+
script = @sql.map { |e| e.strip + ';' }.join $/
|
111
|
+
EachSQL(script).each_with_index do |sql,idx|
|
112
|
+
puts sql
|
113
|
+
puts '-' * 40
|
114
|
+
assert_equal @sql[idx], sql
|
115
|
+
end
|
116
|
+
assert_equal EachSQL(script).to_a, EachSQL(script).map { |e| e }
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_oracle
|
120
|
+
EachSQL(@oracle, :oracle).each_with_index do |sql,idx|
|
121
|
+
puts sql
|
122
|
+
puts '-' * 40
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_mysql
|
127
|
+
EachSQL(@mysql, :mysql).each_with_index do |sql,idx|
|
128
|
+
puts sql
|
129
|
+
puts '-' * 40
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def _test_postgres
|
134
|
+
EachSQL(File.read(File.dirname(__FILE__) + '/postgres.sql'), :postgres).each_with_index do |sql,idx|
|
135
|
+
puts sql
|
136
|
+
puts '-' * 40
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: each_sql
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Junegunn Choi
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-06-15 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: &2161738720 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.0.0
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2161738720
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: jeweler
|
27
|
+
requirement: &2161738240 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.6.2
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2161738240
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rcov
|
38
|
+
requirement: &2161737760 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2161737760
|
47
|
+
description: Enumerate through each SQL statement in SQL scripts.
|
48
|
+
email: junegunn.c@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files:
|
52
|
+
- LICENSE.txt
|
53
|
+
- README.rdoc
|
54
|
+
files:
|
55
|
+
- .document
|
56
|
+
- Gemfile
|
57
|
+
- Gemfile.lock
|
58
|
+
- LICENSE.txt
|
59
|
+
- README.rdoc
|
60
|
+
- Rakefile
|
61
|
+
- VERSION
|
62
|
+
- lib/each_sql.rb
|
63
|
+
- lib/each_sql/each_sql.rb
|
64
|
+
- test/helper.rb
|
65
|
+
- test/postgres.sql
|
66
|
+
- test/test_each_sql.rb
|
67
|
+
homepage: http://github.com/junegunn/each_sql
|
68
|
+
licenses:
|
69
|
+
- MIT
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
hash: -4303771573274782197
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 1.7.2
|
92
|
+
signing_key:
|
93
|
+
specification_version: 3
|
94
|
+
summary: Enumerate through each SQL statement in SQL scripts.
|
95
|
+
test_files: []
|