sequel_oracle_extensions 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Joe Khoobyar
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,17 @@
1
+ = sequel_oracle_extensions
2
+
3
+ Oracle extensions for Sequel, including MERGE statements, optimizer hints, and schema extensions.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Joe Khoobyar. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "sequel_oracle_extensions"
8
+ gem.summary = %Q{Oracle MERGE, optimizer hints, an schema extensions for Sequel}
9
+ gem.description = %Q{Oracle extensions for Sequel, including MERGE statements, optimizer hints, and schema extensions.}
10
+ gem.email = "joe@ankhcraft.com"
11
+ gem.homepage = "http://github.com/joekhoobyar/sequel_oracle_extensions"
12
+ gem.authors = ["Joe Khoobyar"]
13
+ gem.add_dependency "sequel", ">= 3.10.0"
14
+ gem.add_development_dependency "rspec", ">= 2.0.0.beta.8"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'rspec/core/rake_task'
23
+ Rspec::Core::RakeTask.new(:rspec) do |spec|
24
+ spec.pattern = FileList['spec/**/*_spec.rb']
25
+ end
26
+ Rspec::Core::RakeTask.new(:rcov) do |spec|
27
+ spec.pattern = 'spec/**/*_spec.rb'
28
+ spec.rcov = true
29
+ end
30
+
31
+ task :rspec => :check_dependencies
32
+
33
+ task :default => :rspec
34
+
35
+ require 'rake/rdoctask'
36
+ Rake::RDocTask.new do |rdoc|
37
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
38
+
39
+ rdoc.rdoc_dir = 'rdoc'
40
+ rdoc.title = "sequel_oracle_extensions #{version}"
41
+ rdoc.rdoc_files.include('README*')
42
+ rdoc.rdoc_files.include('lib/**/*.rb')
43
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.0
@@ -0,0 +1,72 @@
1
+ require 'sequel'
2
+ Sequel.require 'adapters/shared/oracle'
3
+
4
+ # The hint extension adds support for Oracle hints
5
+ module Sequel
6
+
7
+ [Dataset, Oracle::DatasetMethods].each do |t|
8
+ t.instance_eval do
9
+ constants.grep(/_CLAUSE_METHODS$/).each do |k|
10
+ type = k[0,k.length - 15].downcase
11
+ meth = :"#{type}_hint_sql"
12
+ const_set k, [meth].concat(remove_const(k)) unless const_get(k).include? meth
13
+ end
14
+ end
15
+ end
16
+
17
+ module Oracle
18
+ module DatasetMethods
19
+
20
+ def hint(*args) clone(:hints => _hints(*args){|v| v.dup}) end
21
+ def hint!(*args) @opts[:hints] = _hints(*args){|v| v.dup}; self end
22
+ def hints(*args) clone(:hints => _hints(*args){|v| []}) end
23
+ def hints!(*args) @opts[:hints] = _hints(*args){|v| []}; self end
24
+
25
+ def hint_sql(type, sql)
26
+ if @opts.include? :hints and @opts[:hints].include? type and not @opts[:hints][type].empty?
27
+ sql << " /*+ #{@opts[:hints][type].join ' '} */"
28
+ end
29
+ end
30
+
31
+ %w(select insert update delete merge).map{|k| k.to_sym}.each do |k|
32
+ define_method(:"#{k}_hint") {|*args| hint k, *args}
33
+ define_method(:"#{k}_hint!") {|*args| hint! k, *args}
34
+ define_method(:"#{k}_hints") {|*args| hints k, *args}
35
+ define_method(:"#{k}_hints!") {|*args| hints! k, *args}
36
+ define_method(:"#{k}_hint_sql") {|sql| hint_sql k, sql}
37
+ end
38
+
39
+ protected
40
+
41
+ def _hints(*args, &block)
42
+ type = args.shift if Symbol === args.first
43
+ hints = hints_copy type, &block
44
+ if type.nil?
45
+ args.each do |arg|
46
+ arg = { :select => arg } unless Hash === arg
47
+ arg.each{|k,v| hint_list_add hints[k], v}
48
+ end
49
+ else
50
+ hint_list_add hints[type], args
51
+ end
52
+ hints
53
+ end
54
+
55
+ private
56
+
57
+ def hints_copy(type=nil)
58
+ hints = Hash.new{|h,k| h[k] = []}
59
+ @opts[:hints].each{|k,v| v = yield v if type.nil? or type==k; hints[k] = v} if @opts.include? :hints
60
+ hints
61
+ end
62
+
63
+ def hint_list_add(list, hint)
64
+ case hint
65
+ when String; list.push hint
66
+ when Array; list.concat hint
67
+ else raise Error, "Invalid SQL hint value '#{hints.class.name}': must be an Array or String"
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,204 @@
1
+ require 'sequel'
2
+
3
+ # The merge extension adds support for Oracle's MERGE statement.
4
+ module Sequel
5
+ class Dataset
6
+ MERGE_CLAUSE_METHODS = clause_methods(:merge, %w'target source join update delete insert')
7
+
8
+ def merge(&block)
9
+ execute_dui merge_sql(&block)
10
+ end
11
+
12
+ def merge_using(*values, &block)
13
+ execute_dui merge_using_sql(*values, &block)
14
+ end
15
+
16
+ def merge_into(*values, &block)
17
+ execute_dui merge_into_sql(*values, &block)
18
+ end
19
+
20
+ def merge_sql(*values, &block)
21
+ ms = clone
22
+ ms.opts = { :into=>values.shift, :using=>values.shift, :on=>[values.shift].compact }
23
+ [:update, :insert, :delete].each{|k| ms.opts[k] = values.shift }
24
+ ms.opts.update :defaults=>@opts[:defaults], :overrides=>@opts[:overrides]
25
+
26
+ if block_given?
27
+ ms.extend(MergeBlockCopy)
28
+ ms.instance_eval(&block)
29
+ ms = ms.clone(ms.opts)
30
+ end
31
+
32
+ ms.opts[:into] ||= @opts[:from].first
33
+ ms.opts[:on] << @opts[:where] if @opts[:where]
34
+
35
+ ms.send :_merge_sql
36
+ end
37
+
38
+ def merge_using_sql(using, *values, &block)
39
+ merge_sql @opts[:from].first, using, *values, &block
40
+ end
41
+
42
+ def merge_into_sql(into, on, *values, &block)
43
+ merge_sql into, self, on, *values, &block
44
+ end
45
+
46
+ protected
47
+
48
+ # SQL fragment specifying the target to merge INTO
49
+ def merge_target_sql(sql)
50
+ sql << " INTO #{table_ref(@opts[:into])}"
51
+ end
52
+
53
+ # SQL fragment specifying the source to merge USING
54
+ def merge_source_sql(sql)
55
+ sql << "\nUSING #{table_ref(@opts[:using])}"
56
+ end
57
+
58
+ # SQL fragment specifying what to perform the merge ON
59
+ def merge_join_sql(sql)
60
+ sql << "\nON #{literal(@opts[:on])}"
61
+ end
62
+
63
+ # SQL fragment specifying which target rows to DELETE
64
+ def merge_delete_sql(sql)
65
+ sql << "\nDELETE WHERE #{literal(@opts[:delete])}\n" if @opts[:delete]
66
+ end
67
+
68
+ # The SQL fragment specifying the columns and values to UPDATE
69
+ def merge_update_sql(sql)
70
+ return if not (values = @opts[:update]) or values.empty?
71
+ values = Hash[values] if Array===values and values.all?{|v| Array===v && v.size==2}
72
+ if Hash === values
73
+ values = @opts[:defaults].merge(values) if @opts[:defaults]
74
+ values = values.merge(@opts[:overrides]) if @opts[:overrides]
75
+ # get values from hash
76
+ values = values.map do |k, v|
77
+ "#{k.is_a?(String) && !k.is_a?(LiteralString) ? quote_identifier(k) : literal(k)} = #{literal(v)}"
78
+ end.join(COMMA_SEPARATOR)
79
+ end
80
+ sql << "\nWHEN MATCHED THEN\nUPDATE SET #{values}"
81
+ end
82
+
83
+ # The SQL fragment specifying the columns and values to INSERT
84
+ def merge_insert_sql(sql)
85
+ return if not @opts[:insert] or @opts[:insert].empty?
86
+ columns, values = [], []
87
+ @opts[:insert].each do |k,v|
88
+ columns.push(k.is_a?(String) && !k.is_a?(LiteralString) ? quote_identifier(k) : literal(k))
89
+ values.push(v.is_a?(String) && !v.is_a?(LiteralString) ? quote_identifier(v) : literal(v))
90
+ end
91
+ sql << "\nWHEN NOT MATCHED THEN\nINSERT (#{columns.join(COMMA_SEPARATOR)})"
92
+ sql << "\nVALUES (#{values.join(COMMA_SEPARATOR)})"
93
+ end
94
+
95
+ # The order of methods to call on the MERGE SQL statement
96
+ def merge_clause_methods
97
+ MERGE_CLAUSE_METHODS
98
+ end
99
+
100
+ def _merge_sql
101
+ _merge_alias_tables
102
+ @opts[:on] = @opts[:on].inject(nil) do |a,b|
103
+ b = _merge_expressions b
104
+ b = filter_expr((Array===b && b.size==1) ? b.first : b)
105
+ a ? SQL::BooleanExpression.new(:AND, a, b) : b
106
+ end
107
+ @opts[:insert] = @opts[:defaults].merge(@opts[:insert]) if @opts[:defaults]
108
+ @opts[:insert] = @opts[:insert].merge(@opts[:overrides]) if @opts[:overrides]
109
+ [:insert, :update, :delete].each do |k|
110
+ @opts[k] = _merge_expressions @opts[k], k!=:delete || nil
111
+ end
112
+
113
+ clause_sql(:merge)
114
+ end
115
+
116
+ # Utility method to create and/or apply table aliaseses for expressions.
117
+ def _merge_expressions(expr,apply_aliases=nil)
118
+ if Symbol===expr
119
+ expr = {expr => expr}
120
+ elsif expr.is_a?(Array) and not expr.empty? and expr.all?{|x| x.is_a?(Symbol)}
121
+ expr = expr.inject({}){|h,k| h[k]=k; h}
122
+ elsif expr.nil? or LiteralString===expr
123
+ return expr
124
+ end
125
+ apply_aliases = Sequel.condition_specifier?(expr) if apply_aliases.nil?
126
+ apply_aliases ? _merge_column_pairs(expr) : expr
127
+ end
128
+
129
+ # Utility method to create any necessary table aliases.
130
+ def _merge_alias_tables
131
+ alias_num = @opts[:num_dataset_sources]||0
132
+ [:into, :using].each do |k|
133
+ if Symbol===@opts[k]
134
+ u_table, u_column, u_alias = split_symbol(@opts[k])
135
+ @opts[k] = "#{@opts[k]}___#{dataset_alias(alias_num += 1)}".to_sym unless u_alias
136
+ else
137
+ @opts[k] = @opts[k].as dataset_alias(alias_num += 1) unless @opts[k].respond_to? :aliaz
138
+ end
139
+ end
140
+ end
141
+
142
+ private
143
+
144
+ # Utility method to qualify column pairs to the target and source table aliases.
145
+ def _merge_table_aliases
146
+ @opts.values_at(:into, :using).map{|t| Symbol===t ? split_symbol(t).last : t.aliaz.to_s}
147
+ end
148
+
149
+ # Utility method to qualify column pairs to the target and source table aliases.
150
+ def _merge_column_pairs(pairs)
151
+ t1, t2 = _merge_table_aliases
152
+ merged = pairs.collect do |k, v|
153
+ k = qualified_column_name(k, t1) if k.is_a?(Symbol)
154
+ v = qualified_column_name(v, t2) if v.is_a?(Symbol)
155
+ [k,v]
156
+ end
157
+ merged = Hash[*merged.flatten] if Hash === pairs
158
+ merged
159
+ end
160
+
161
+ # Module used by Dataset#merge that has the effect of making all
162
+ # dataset methods into !-style methods that modify the receiver.
163
+ module MergeBlockCopy
164
+ def each; raise Error, "each cannot be invoked inside a merge block." end
165
+ def into(t) @opts[:into] = t end
166
+ def using(t) @opts[:using] = t end
167
+
168
+ %w'on update delete'.each do |m|
169
+ module_eval <<-eodef
170
+ def #{m}(*args, &block)
171
+ @opts[:#{m}] = if block; then Sequel.virtual_row(&block)
172
+ elsif Hash===args.first; then args.first
173
+ else args
174
+ end
175
+ end
176
+ eodef
177
+ end
178
+
179
+ def insert(*args, &block)
180
+ args = [ args ] unless args.empty?
181
+ args.push([Sequel.virtual_row(&block)]) if block
182
+ @opts[:insert] = args.inject([]) do |r,a|
183
+ if Hash === a.first
184
+ raise Error, "Invalid insert arguments" unless a.size == 1
185
+ r.concat a.first.to_a
186
+ elsif a.size == 2
187
+ raise Error, "Invalid insert arguments" unless a.all?{|v| Array===v && v.size==2}
188
+ a.first.each_with_index{|k,i| r.push([k,a.last[i]]) }
189
+ r
190
+ else
191
+ raise Error, "Invalid insert arguments"
192
+ end
193
+ end
194
+ end
195
+
196
+ # Merge the given options into the receiver's options and return the receiver
197
+ # instead of cloning the receiver.
198
+ def clone(opts = nil)
199
+ @opts.merge!(opts)
200
+ self
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,61 @@
1
+ require 'sequel'
2
+ Sequel.require 'adapters/shared/oracle'
3
+
4
+ # The oracle_schemata extension adds some schema related methods to the Oracle database adapater.
5
+ module Sequel
6
+ module Oracle
7
+ module DatabaseMethods
8
+
9
+ SELECT_INDEXES_SQL = %q{
10
+ SELECT i.index_name, i.status, i.uniqueness, ic.column_name
11
+ FROM all_indexes i
12
+ INNER JOIN all_ind_columns ic ON ic.index_owner = i.owner AND ic.index_name = i.index_name
13
+ WHERE i.table_name = ? AND i.dropped = 'NO' AND NOT EXISTS (
14
+ SELECT uc.index_name FROM all_constraints uc
15
+ WHERE uc.index_name = i.index_name AND uc.owner = i.owner AND uc.constraint_type = 'P'
16
+ )
17
+ ORDER BY status DESC, index_name, ic.column_position
18
+ }.freeze
19
+
20
+ SELECT_PRIMARY_KEY_SQL = %q{
21
+ SELECT c.constraint_name, c.index_name, c.status, cc.column_name
22
+ FROM all_constraints c
23
+ INNER JOIN all_cons_columns cc ON cc.owner = c.owner AND cc.constraint_name = c.constraint_name
24
+ WHERE c.table_name = ? AND c.constraint_type = 'P'
25
+ ORDER BY status DESC, constraint_name, cc.position
26
+ }.freeze
27
+
28
+ # Returns the indexes for the given table, excluding any primary keys.
29
+ def indexes(table)
30
+ m = output_identifier_meth
31
+ table = m[table]
32
+ ixs = Hash.new{|h,k| h[k] = {:table_name=>table, :columns=>[]}}
33
+ metadata_dataset.with_sql(SELECT_INDEXES_SQL, table.to_s.upcase).each do |r|
34
+ r = Hash[ r.map{|k,v| [k, (k==:index_name || k==:column_name) ? m[v] : v]} ]
35
+ ix = ixs[m.call r.delete(:index_name)]
36
+ ix[:valid] = r.delete(:status)=='VALID'
37
+ ix[:unique] = r.delete(:uniqueness)=='UNIQUE'
38
+ ix[:columns] << r.delete(:column_name)
39
+ ix.update r
40
+ end
41
+ ixs
42
+ end
43
+
44
+ # Returns the primary key for the given table.
45
+ def primary_key(table)
46
+ m = output_identifier_meth
47
+ table = m[table]
48
+ pks = Hash.new{|h,k| h[k] = {:table_name=>table, :columns=>[]}}
49
+ metadata_dataset.with_sql(SELECT_PRIMARY_KEY_SQL, table.to_s.upcase).each do |r|
50
+ r = Hash[ r.map{|k,v| [k, (k==:status || v.nil? || v=='') ? v : m[v]]} ]
51
+ pk = pks[m.call r.delete(:constraint_name)]
52
+ pk[:enabled] = r.delete(:status)=='ENABLED'
53
+ pk[:columns] << r.delete(:column_name)
54
+ pk.update r
55
+ end
56
+ return pks.first.last if pks.size <= 1 or pks[0][:enabled] == pks[1][:enabled]
57
+ pks
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,4 @@
1
+ require 'sequel'
2
+ require 'sequel/oracle_extensions/schemata'
3
+ require 'sequel/oracle_extensions/merge'
4
+ require 'sequel/oracle_extensions/hints'
@@ -0,0 +1 @@
1
+ require 'sequel/oracle_extensions'
@@ -0,0 +1,64 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{sequel_oracle_extensions}
8
+ s.version = "0.5.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Joe Khoobyar"]
12
+ s.date = %q{2010-05-24}
13
+ s.description = %q{Oracle extensions for Sequel, including MERGE statements, optimizer hints, and schema extensions.}
14
+ s.email = %q{joe@ankhcraft.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ ".rspec",
23
+ "LICENSE",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "lib/sequel/oracle_extensions.rb",
28
+ "lib/sequel/oracle_extensions/hints.rb",
29
+ "lib/sequel/oracle_extensions/merge.rb",
30
+ "lib/sequel/oracle_extensions/schemata.rb",
31
+ "lib/sequel_oracle_extensions.rb",
32
+ "sequel_oracle_extensions.gemspec",
33
+ "spec/sequel/oracle_extensions/hints_spec.rb",
34
+ "spec/sequel/oracle_extensions/merge_spec.rb",
35
+ "spec/spec_helper.rb"
36
+ ]
37
+ s.homepage = %q{http://github.com/joekhoobyar/sequel_oracle_extensions}
38
+ s.rdoc_options = ["--charset=UTF-8"]
39
+ s.require_paths = ["lib"]
40
+ s.rubygems_version = %q{1.3.6}
41
+ s.summary = %q{Oracle MERGE, optimizer hints, an schema extensions for Sequel}
42
+ s.test_files = [
43
+ "spec/sequel/oracle_extensions/hints_spec.rb",
44
+ "spec/sequel/oracle_extensions/merge_spec.rb",
45
+ "spec/spec_helper.rb"
46
+ ]
47
+
48
+ if s.respond_to? :specification_version then
49
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
50
+ s.specification_version = 3
51
+
52
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
53
+ s.add_runtime_dependency(%q<sequel>, [">= 3.10.0"])
54
+ s.add_development_dependency(%q<rspec>, [">= 2.0.0.beta.8"])
55
+ else
56
+ s.add_dependency(%q<sequel>, [">= 3.10.0"])
57
+ s.add_dependency(%q<rspec>, [">= 2.0.0.beta.8"])
58
+ end
59
+ else
60
+ s.add_dependency(%q<sequel>, [">= 3.10.0"])
61
+ s.add_dependency(%q<rspec>, [">= 2.0.0.beta.8"])
62
+ end
63
+ end
64
+
@@ -0,0 +1,157 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+ require 'sequel/oracle_extensions/merge'
3
+ require 'sequel/oracle_extensions/hints'
4
+
5
+ describe "Sequel::OracleExtensions::Hints" do
6
+
7
+ CLAUSES = %w(SELECT INSERT UPDATE DELETE MERGE)
8
+ TYPES = CLAUSES.map{|clause| clause.downcase.intern}
9
+
10
+ before(:all) do
11
+ @db = Sequel.connect(DATABASE_URL)
12
+ end
13
+
14
+ def apply_hints!(*args)
15
+ @old_hints = @ds.opts[:hints]
16
+ @new_ds = @ds.__send__(@method, *args)
17
+
18
+ @new_ds.should be_kind_of(Sequel::Dataset)
19
+ @new_ds.opts[:hints].should_not be_empty
20
+ end
21
+
22
+ it "hooks into dataset clause methods" do
23
+ [Sequel::Dataset, Sequel::Oracle::DatasetMethods].each do |klass|
24
+ CLAUSES.each do |clause|
25
+ next unless klass.const_defined?(k = :"#{clause}_CLAUSE_METHODS")
26
+ klass.const_get(k).first.should == "#{clause.downcase}_hint_sql".intern
27
+ end
28
+ end
29
+ end
30
+
31
+ share_examples_for "dataset modifying" do
32
+ after(:each) do
33
+ @ds.should equal(@new_ds)
34
+ @ds.opts[:hints].should_not == @old_hints
35
+ end
36
+ end
37
+
38
+ share_examples_for "dataset cloning" do
39
+ after(:each) do
40
+ @ds.should_not equal(@new_ds)
41
+ @ds.opts[:hints].should == @old_hints
42
+ end
43
+ end
44
+
45
+ share_examples_for "standard callspec" do
46
+ it "callspec (String) applies :select hints" do
47
+ apply_hints! @hints.first
48
+ hints_to_check(@new_ds, :select, @hints[0,1]).should == @hints[0,1]
49
+ end
50
+ it "callspec (String, ...) applies :select hints" do
51
+ apply_hints! *@hints
52
+ hints_to_check(@new_ds, :select, @hints).should == @hints
53
+ end
54
+ it "callspec (clause, String) applies clause hints" do
55
+ TYPES.each do |type|
56
+ apply_hints! type, @hints.first
57
+ hints_to_check(@new_ds, type, @hints[0,1]).should == @hints[0,1]
58
+ end
59
+ end
60
+ it "callspec (clause, String, ...) applies clause hints" do
61
+ TYPES.each do |type|
62
+ apply_hints! type, *@hints
63
+ hints_to_check(@new_ds, type, @hints).should == @hints
64
+ end
65
+ end
66
+ end
67
+
68
+ share_examples_for "clause-specific callspec" do
69
+ it "callspec (String) applies hints" do
70
+ apply_hints! @hints.first
71
+ hints_to_check(@new_ds, @clause, @hints[0,1]).should == @hints[0,1]
72
+ end
73
+ it "callspec (String, ...) applies hints" do
74
+ apply_hints! *@hints
75
+ hints_to_check(@new_ds, @clause, @hints).should == @hints
76
+ end
77
+ end
78
+
79
+ describe "hints" do
80
+ before(:each){ @ds, @hints = @db[:dual], ['foo', 'bar'] }
81
+
82
+ COMMON_GROUP_BODY = Proc.new do |group,name|
83
+ group.class_eval do
84
+ describe "##{name}" do
85
+ before(:each){ @method = :"#{name}" }
86
+ it_should_behave_like "dataset cloning"
87
+ it_should_behave_like "standard callspec"
88
+ end
89
+ describe "##{name}!" do
90
+ before(:each){ @method = :"#{name}!" }
91
+ it_should_behave_like "dataset modifying"
92
+ it_should_behave_like "standard callspec"
93
+ end
94
+ TYPES.each do |clause|
95
+ describe "##{clause}_#{name}" do
96
+ before(:each){ @clause, @method = clause, :"#{clause}_#{name}"}
97
+ it_should_behave_like "dataset cloning"
98
+ it_should_behave_like "clause-specific callspec"
99
+ end
100
+ describe "##{clause}_#{name}!" do
101
+ before(:each){ @clause, @method = clause, :"#{clause}_#{name}!"}
102
+ it_should_behave_like "dataset modifying"
103
+ it_should_behave_like "clause-specific callspec"
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ describe "adding hints" do
110
+ def hints_to_check(ds, type, input)
111
+ ds.opts[:hints][type][(@orig_hints[type].length rescue 0), input.length]
112
+ end
113
+ COMMON_GROUP_BODY.call self, :hint
114
+ end
115
+
116
+ describe "overwriting hints" do
117
+ def hints_to_check(ds, type, input)
118
+ ds.opts[:hints][type]
119
+ end
120
+ COMMON_GROUP_BODY.call self, :hints
121
+ end
122
+
123
+ describe "#hint_sql" do
124
+ it "generates clause-specific hint SQL" do
125
+ # set them all up front so we can test whether they get mixed up.
126
+ TYPES.each do |type| @ds.hints! type, "hint for #{type}" end
127
+ TYPES.each do |type|
128
+ sql = (clause = type.to_s.upcase).dup
129
+ @ds.hint_sql(type, sql).should equal(sql)
130
+ sql.should == "#{clause} /*+ hint for #{type} */"
131
+ end
132
+ end
133
+
134
+ it "skips empty hints" do
135
+ TYPES.each do |type|
136
+ sql = (clause = type.to_s.upcase).dup
137
+ @ds.hint_sql(type, sql).should be_nil
138
+ sql.should == clause
139
+ end
140
+ end
141
+
142
+ TYPES.each do |type|
143
+ it "is called by ##{type}_hint_sql" do
144
+ clause = type.to_s.upcase
145
+ @ds.should_receive(:hint_sql).with(type, clause)
146
+ @ds.__send__ :"#{type}_hint_sql", clause
147
+ end
148
+ it "is called by ##{type}_sql" do
149
+ args, clause = [], type.to_s.upcase
150
+ @ds.should_receive(:hint_sql).with(type, clause)
151
+ args.push :dual, :dual, :x if type == :merge
152
+ @ds.__send__ :"#{type}_sql", *args
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,8 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+ require 'sequel/oracle_extensions/merge'
3
+
4
+ describe "Sequel::OracleExtensions::Merge" do
5
+ before(:all) do
6
+ @db = Sequel.connect(DATABASE_URL)
7
+ end
8
+ end
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ require 'pp'
3
+ begin require 'win32console' and include Win32::Console::ANSI
4
+ rescue LoadError
5
+ end if RUBY_PLATFORM =~ /msvc|mingw|cygwin|win32/
6
+
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
9
+ require 'sequel'
10
+ require "rspec"
11
+
12
+ DATABASE_URL = begin
13
+ user = ENV['DATABASE_USER'] || 'hr'
14
+ password = ENV['DATABASE_PASSWORD'] || 'hr'
15
+ name = ENV['DATABASE_NAME'] || 'xe'
16
+ host = ENV['DATABASE_HOST'] || 'localhost'
17
+ port = ':'+ENV['DATABASE_PORT'] rescue nil
18
+ "oracle://#{user}:#{password}@#{host}#{port}/#{name}"
19
+ end
20
+
21
+ Rspec.configure do |config|
22
+ require 'rspec/expectations'
23
+ config.include Rspec::Matchers
24
+ config.mock_with :rspec
25
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sequel_oracle_extensions
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 5
8
+ - 0
9
+ version: 0.5.0
10
+ platform: ruby
11
+ authors:
12
+ - Joe Khoobyar
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-05-24 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: sequel
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 3
29
+ - 10
30
+ - 0
31
+ version: 3.10.0
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rspec
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 2
43
+ - 0
44
+ - 0
45
+ - beta
46
+ - 8
47
+ version: 2.0.0.beta.8
48
+ type: :development
49
+ version_requirements: *id002
50
+ description: Oracle extensions for Sequel, including MERGE statements, optimizer hints, and schema extensions.
51
+ email: joe@ankhcraft.com
52
+ executables: []
53
+
54
+ extensions: []
55
+
56
+ extra_rdoc_files:
57
+ - LICENSE
58
+ - README.rdoc
59
+ files:
60
+ - .document
61
+ - .gitignore
62
+ - .rspec
63
+ - LICENSE
64
+ - README.rdoc
65
+ - Rakefile
66
+ - VERSION
67
+ - lib/sequel/oracle_extensions.rb
68
+ - lib/sequel/oracle_extensions/hints.rb
69
+ - lib/sequel/oracle_extensions/merge.rb
70
+ - lib/sequel/oracle_extensions/schemata.rb
71
+ - lib/sequel_oracle_extensions.rb
72
+ - sequel_oracle_extensions.gemspec
73
+ - spec/sequel/oracle_extensions/hints_spec.rb
74
+ - spec/sequel/oracle_extensions/merge_spec.rb
75
+ - spec/spec_helper.rb
76
+ has_rdoc: true
77
+ homepage: http://github.com/joekhoobyar/sequel_oracle_extensions
78
+ licenses: []
79
+
80
+ post_install_message:
81
+ rdoc_options:
82
+ - --charset=UTF-8
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ segments:
90
+ - 0
91
+ version: "0"
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ segments:
97
+ - 0
98
+ version: "0"
99
+ requirements: []
100
+
101
+ rubyforge_project:
102
+ rubygems_version: 1.3.6
103
+ signing_key:
104
+ specification_version: 3
105
+ summary: Oracle MERGE, optimizer hints, an schema extensions for Sequel
106
+ test_files:
107
+ - spec/sequel/oracle_extensions/hints_spec.rb
108
+ - spec/sequel/oracle_extensions/merge_spec.rb
109
+ - spec/spec_helper.rb