sequel 2.6.0 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/CHANGELOG +64 -0
  2. data/Rakefile +1 -1
  3. data/lib/sequel_core/adapters/jdbc.rb +6 -2
  4. data/lib/sequel_core/adapters/jdbc/oracle.rb +23 -0
  5. data/lib/sequel_core/adapters/oracle.rb +4 -77
  6. data/lib/sequel_core/adapters/postgres.rb +39 -26
  7. data/lib/sequel_core/adapters/shared/mssql.rb +0 -1
  8. data/lib/sequel_core/adapters/shared/mysql.rb +1 -1
  9. data/lib/sequel_core/adapters/shared/oracle.rb +82 -0
  10. data/lib/sequel_core/adapters/shared/postgres.rb +65 -46
  11. data/lib/sequel_core/core_ext.rb +10 -0
  12. data/lib/sequel_core/core_sql.rb +7 -0
  13. data/lib/sequel_core/database.rb +22 -0
  14. data/lib/sequel_core/database/schema.rb +1 -1
  15. data/lib/sequel_core/dataset.rb +29 -11
  16. data/lib/sequel_core/dataset/sql.rb +27 -7
  17. data/lib/sequel_core/migration.rb +20 -2
  18. data/lib/sequel_core/object_graph.rb +24 -10
  19. data/lib/sequel_core/schema/generator.rb +22 -9
  20. data/lib/sequel_core/schema/sql.rb +13 -9
  21. data/lib/sequel_core/sql.rb +27 -2
  22. data/lib/sequel_model/association_reflection.rb +251 -141
  23. data/lib/sequel_model/associations.rb +114 -61
  24. data/lib/sequel_model/base.rb +25 -21
  25. data/lib/sequel_model/eager_loading.rb +17 -40
  26. data/lib/sequel_model/hooks.rb +25 -24
  27. data/lib/sequel_model/record.rb +29 -51
  28. data/lib/sequel_model/schema.rb +1 -1
  29. data/lib/sequel_model/validations.rb +13 -3
  30. data/spec/adapters/postgres_spec.rb +104 -18
  31. data/spec/adapters/spec_helper.rb +4 -1
  32. data/spec/integration/eager_loader_test.rb +5 -4
  33. data/spec/integration/spec_helper.rb +4 -1
  34. data/spec/sequel_core/connection_pool_spec.rb +24 -24
  35. data/spec/sequel_core/core_sql_spec.rb +12 -0
  36. data/spec/sequel_core/dataset_spec.rb +77 -2
  37. data/spec/sequel_core/expression_filters_spec.rb +6 -0
  38. data/spec/sequel_core/object_graph_spec.rb +40 -2
  39. data/spec/sequel_core/schema_spec.rb +13 -0
  40. data/spec/sequel_model/association_reflection_spec.rb +8 -8
  41. data/spec/sequel_model/associations_spec.rb +164 -3
  42. data/spec/sequel_model/caching_spec.rb +2 -1
  43. data/spec/sequel_model/eager_loading_spec.rb +107 -3
  44. data/spec/sequel_model/hooks_spec.rb +38 -22
  45. data/spec/sequel_model/model_spec.rb +11 -35
  46. data/spec/sequel_model/plugins_spec.rb +4 -2
  47. data/spec/sequel_model/record_spec.rb +8 -5
  48. data/spec/sequel_model/validations_spec.rb +25 -0
  49. data/spec/spec_config.rb +4 -3
  50. metadata +21 -19
data/CHANGELOG CHANGED
@@ -1,3 +1,67 @@
1
+ === 2.7.0 (2008-11-03)
2
+
3
+ * Transform AssociationReflection from a single class to a class hierarchy (jeremyevans)
4
+
5
+ * Optimize Date object creation in PostgreSQL adapter (jeremyevans)
6
+
7
+ * Allow easier creation of custom association types, though support for them may still be suboptimal (jeremyevans)
8
+
9
+ * Add :eager_grapher option to associations, which the user can use to override the default eager_graph code (jeremyevans)
10
+
11
+ * Associations are now inherited when a model class is subclassed (jeremyevans)
12
+
13
+ * Instance methods added by associations are now added to an anonymous module the class includes, allowing you to override them and use super (jeremyevans)
14
+
15
+ * Add #add_graph_aliases (select_more for graphs), and allow use of arbitrary expressions when graphing (jeremyevans)
16
+
17
+ * Fix a corner case where the wrong table name is used in eager_graph (jeremyevans)
18
+
19
+ * Make Dataset#join_table take an option hash instead of a table_alias argument, add support for :implicit_qualifier option (jeremyevans)
20
+
21
+ * Add :left_primary_key and :right_primary_key options to many_to_many associations (jeremyevans)
22
+
23
+ * Add :primary_key option to one_to_many and many_to_one associations (jeremyevans)
24
+
25
+ * Make after_load association callbacks take effect when eager loading via eager (jeremyevans)
26
+
27
+ * Add a :uniq association option to many_to_many associations (jeremyevans)
28
+
29
+ * Support using any expression as the argument to Symbol#like (jeremyevans)
30
+
31
+ * Much better support for multiple schemas in PostgreSQL (jeremyevans) (#243)
32
+
33
+ * The first argument to Model#initalize can no longer be nil, it must be a hash if it is given (jeremyevans)
34
+
35
+ * Remove Sequel::Model.lazy_load_schema= setting (jeremyevans)
36
+
37
+ * Lazily load model instance options such as raise_on_save_failure, for better performance (jeremyevans)
38
+
39
+ * Make Model::Validiation::Errors more Rails-compatible (jeremyevans)
40
+
41
+ * Refactor model hooks for performance (jeremyevans)
42
+
43
+ * Major performance enhancement when fetching rows using PostgreSQL (jeremyevans)
44
+
45
+ * Don't typecast serialized columns in models (jeremyevans)
46
+
47
+ * Add Array#sql_array to handle ruby arrays of all two pairs as SQL arrays (jeremyevans) (#245)
48
+
49
+ * Add ComplexExpression#== and #eql?, for checking equality (rubymage) (#244)
50
+
51
+ * Allow full text search on PostgreSQL to include rows where a search column is NULL (jeremyevans)
52
+
53
+ * PostgreSQL full text search queries with multiple columns are joined with space to prevent joining border words to one (michalbugno)
54
+
55
+ * Don't modify a dataset's cached column information if calling #each with an option that modifies the columns (jeremyevans)
56
+
57
+ * The PostgreSQL adapter will now generally default to using a unix socket in /tmp if no host is specified, instead of a tcp socket to localhost (jeremyevans)
58
+
59
+ * Make Dataset#sql call Dataset#select_sql instead of being an alias, to allow for easier subclassing (jeremyevans)
60
+
61
+ * Split Oracle adapter into shared and unshared parts, so Oracle is better supported when using JDBC (jeremyevans)
62
+
63
+ * Fix automatic loading of Oracle driver when using JDBC adapter (bburton333) (#242)
64
+
1
65
  === 2.6.0 (2008-10-11)
2
66
 
3
67
  * Make the sqlite adapter respect the Sequel.datetime_class setting, for timestamp and datetime types (jeremyevans)
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ require "fileutils"
12
12
  include FileUtils
13
13
 
14
14
  NAME = 'sequel'
15
- VERS = '2.6.0'
15
+ VERS = '2.7.0'
16
16
  CLEAN.include ["**/.*.sw?", "pkg", ".config", "rdoc", "coverage", "www/public/*.html"]
17
17
  RDOC_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', \
18
18
  'Sequel: The Database Toolkit for Ruby', '--main', 'README']
@@ -3,7 +3,7 @@ require 'java'
3
3
  module Sequel
4
4
  # Houses Sequel's JDBC support when running on JRuby.
5
5
  # Support for individual database types is done using sub adapters.
6
- # PostgreSQL, MySQL, SQLite, and MSSQL all have relatively good support,
6
+ # PostgreSQL, MySQL, SQLite, Oracle, and MSSQL all have relatively good support,
7
7
  # close the the level supported by the native adapter.
8
8
  # PostgreSQL, MySQL, SQLite can load necessary support using
9
9
  # the jdbc-* gem, if it is installed, though they will work if you
@@ -51,7 +51,11 @@ module Sequel
51
51
  JDBC.load_gem('sqlite3')
52
52
  org.sqlite.JDBC
53
53
  end,
54
- :oracle=>proc{oracle.jdbc.driver.OracleDriver},
54
+ :oracle=>proc do |db|
55
+ require 'sequel_core/adapters/jdbc/oracle'
56
+ db.extend(Sequel::JDBC::Oracle::DatabaseMethods)
57
+ Java::oracle.jdbc.driver.OracleDriver
58
+ end,
55
59
  :sqlserver=>proc do |db|
56
60
  require 'sequel_core/adapters/shared/mssql'
57
61
  db.extend(Sequel::MSSQL::DatabaseMethods)
@@ -0,0 +1,23 @@
1
+ require 'sequel_core/adapters/shared/oracle'
2
+
3
+ module Sequel
4
+ module JDBC
5
+ # Database and Dataset support for Oracle databases accessed via JDBC.
6
+ module Oracle
7
+ # Instance methods for Oracle Database objects accessed via JDBC.
8
+ module DatabaseMethods
9
+ include Sequel::Oracle::DatabaseMethods
10
+
11
+ # Return Sequel::JDBC::Oracle::Dataset object with the given opts.
12
+ def dataset(opts=nil)
13
+ Sequel::JDBC::Oracle::Dataset.new(self, opts)
14
+ end
15
+ end
16
+
17
+ # Dataset class for Oracle datasets accessed via JDBC.
18
+ class Dataset < JDBC::Dataset
19
+ include Sequel::Oracle::DatasetMethods
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,8 +1,10 @@
1
1
  require 'oci8'
2
+ require 'sequel_core/adapters/shared/oracle'
2
3
 
3
4
  module Sequel
4
5
  module Oracle
5
6
  class Database < Sequel::Database
7
+ include DatabaseMethods
6
8
  set_adapter_scheme :oracle
7
9
 
8
10
  def connect(server)
@@ -37,16 +39,6 @@ module Sequel
37
39
  end
38
40
  alias_method :do, :execute
39
41
 
40
- def tables
41
- from(:tab).select(:tname).filter(:tabtype => 'TABLE').map do |r|
42
- r[:tname].downcase.to_sym
43
- end
44
- end
45
-
46
- def table_exists?(name)
47
- from(:tab).filter(:tname => name.to_s.upcase, :tabtype => 'TABLE').count > 0
48
- end
49
-
50
42
  def transaction(server=nil)
51
43
  synchronize(server) do |conn|
52
44
  return yield(conn) if @transactions.include?(Thread.current)
@@ -68,6 +60,8 @@ module Sequel
68
60
  end
69
61
 
70
62
  class Dataset < Sequel::Dataset
63
+ include DatasetMethods
64
+
71
65
  def literal(v)
72
66
  case v
73
67
  when OraDate
@@ -92,73 +86,6 @@ module Sequel
92
86
  end
93
87
  self
94
88
  end
95
-
96
- def empty?
97
- db[:dual].where(exists).get(1) == nil
98
- end
99
-
100
- # Formats a SELECT statement using the given options and the dataset
101
- # options.
102
- def select_sql(opts = nil)
103
- opts = opts ? @opts.merge(opts) : @opts
104
-
105
- if sql = opts[:sql]
106
- return sql
107
- end
108
-
109
- columns = opts[:select]
110
- select_columns = columns ? column_list(columns) : WILDCARD
111
- sql = opts[:distinct] ? \
112
- "SELECT DISTINCT #{select_columns}" : \
113
- "SELECT #{select_columns}"
114
-
115
- if opts[:from]
116
- sql << " FROM #{source_list(opts[:from])}"
117
- end
118
-
119
- if join = opts[:join]
120
- join.each{|j| sql << literal(j)}
121
- end
122
-
123
- if where = opts[:where]
124
- sql << " WHERE #{literal(where)}"
125
- end
126
-
127
- if group = opts[:group]
128
- sql << " GROUP BY #{expression_list(group)}"
129
- end
130
-
131
- if having = opts[:having]
132
- sql << " HAVING #{literal(having)}"
133
- end
134
-
135
- if union = opts[:union]
136
- sql << (opts[:union_all] ? \
137
- " UNION ALL #{union.sql}" : " UNION #{union.sql}")
138
- elsif intersect = opts[:intersect]
139
- sql << (opts[:intersect_all] ? \
140
- " INTERSECT ALL #{intersect.sql}" : " INTERSECT #{intersect.sql}")
141
- elsif except = opts[:except]
142
- sql << (opts[:except_all] ? \
143
- " EXCEPT ALL #{except.sql}" : " EXCEPT #{except.sql}")
144
- end
145
-
146
- if order = opts[:order]
147
- sql << " ORDER BY #{expression_list(order)}"
148
- end
149
-
150
- if limit = opts[:limit]
151
- if (offset = opts[:offset]) && (offset > 0)
152
- sql = "SELECT * FROM (SELECT raw_sql_.*, ROWNUM raw_rnum_ FROM(#{sql}) raw_sql_ WHERE ROWNUM <= #{limit + offset}) WHERE raw_rnum_ > #{offset}"
153
- else
154
- sql = "SELECT * FROM (#{sql}) WHERE ROWNUM <= #{limit}"
155
- end
156
- end
157
-
158
- sql
159
- end
160
-
161
- alias sql select_sql
162
89
  end
163
90
  end
164
91
  end
@@ -83,7 +83,7 @@ module Sequel
83
83
 
84
84
  # Hash with integer keys and proc values for converting PostgreSQL types.
85
85
  PG_TYPES = {
86
- 16 => lambda{ |s| Postgres.string_to_bool(s) }, # boolean
86
+ 16 => lambda{ |s| s == 't' }, # boolean
87
87
  17 => lambda{ |s| Adapter.unescape_bytea(s).to_blob }, # bytea
88
88
  20 => lambda{ |s| s.to_i }, # int8
89
89
  21 => lambda{ |s| s.to_i }, # int2
@@ -93,7 +93,7 @@ module Sequel
93
93
  700 => lambda{ |s| s.to_f }, # float4
94
94
  701 => lambda{ |s| s.to_f }, # float8
95
95
  790 => lambda{ |s| s.to_d }, # money
96
- 1082 => lambda{ |s| s.to_date }, # date
96
+ 1082 => lambda{ |s| @use_iso_date_format ? Date.new(*s.split("-").map{|x| x.to_i}) : s.to_date }, # date
97
97
  1083 => lambda{ |s| s.to_time }, # time without time zone
98
98
  1114 => lambda{ |s| s.to_sequel_time }, # timestamp without time zone
99
99
  1184 => lambda{ |s| s.to_sequel_time }, # timestamp with time zone
@@ -102,16 +102,12 @@ module Sequel
102
102
  1700 => lambda{ |s| s.to_d }, # numeric
103
103
  }
104
104
 
105
- # Module method for converting a PostgreSQL string to a boolean value.
106
- def self.string_to_bool(s)
107
- if(s.blank?)
108
- nil
109
- elsif(s.downcase == 't' || s.downcase == 'true')
110
- true
111
- else
112
- false
113
- end
114
- end
105
+ @use_iso_date_format = true
106
+
107
+ # As an optimization, Sequel sets the date style to ISO, so that PostgreSQL provides
108
+ # the date in a known format that Sequel can parse faster. This can be turned off
109
+ # if you require a date style other than ISO.
110
+ metaattr_accessor :use_iso_date_format
115
111
 
116
112
  # PGconn subclass for connection specific methods used with the
117
113
  # pg, postgres, or postgres-pr driver.
@@ -119,6 +115,13 @@ module Sequel
119
115
  include Sequel::Postgres::AdapterMethods
120
116
  self.translate_results = false if respond_to?(:translate_results=)
121
117
 
118
+ # Apply connection settings for this connection. Current sets
119
+ # the date style to ISO in order make Date object creation in ruby faster,
120
+ # if Postgres.use_iso_date_format is true.
121
+ def apply_connection_settings
122
+ async_exec("SET DateStyle = 'ISO, YMD'") if Postgres.use_iso_date_format
123
+ end
124
+
122
125
  # Execute the given SQL with this connection. If a block is given,
123
126
  # yield the results, otherwise, return the number of changed rows.
124
127
  def execute(sql, args=nil)
@@ -138,6 +141,12 @@ module Sequel
138
141
  q.clear
139
142
  end
140
143
  end
144
+
145
+ # Reapply the connection settings if the connection is reset.
146
+ def reset(*args, &block)
147
+ super(*args, &block)
148
+ apply_connection_settings
149
+ end
141
150
 
142
151
  if SEQUEL_POSTGRES_USES_PG
143
152
  # Hash of prepared statements for this connection. Keys are
@@ -170,14 +179,14 @@ module Sequel
170
179
  @primary_keys = {}
171
180
  @primary_key_sequences = {}
172
181
  end
173
-
182
+
174
183
  # Connects to the database. In addition to the standard database
175
184
  # options, using the :encoding or :charset option changes the
176
185
  # client encoding for the connection.
177
186
  def connect(server)
178
187
  opts = server_opts(server)
179
188
  conn = Adapter.connect(
180
- opts[:host] || 'localhost',
189
+ (opts[:host] unless opts[:host].blank?),
181
190
  opts[:port] || 5432,
182
191
  nil, '',
183
192
  opts[:database],
@@ -185,8 +194,13 @@ module Sequel
185
194
  opts[:password]
186
195
  )
187
196
  if encoding = opts[:encoding] || opts[:charset]
188
- conn.set_client_encoding(encoding)
197
+ if conn.respond_to?(:set_client_encoding)
198
+ conn.set_client_encoding(encoding)
199
+ else
200
+ conn.async_exec("set client_encoding to '#{encoding}'")
201
+ end
189
202
  end
203
+ conn.apply_connection_settings
190
204
  conn.db = self
191
205
  conn
192
206
  end
@@ -280,21 +294,20 @@ module Sequel
280
294
  class Dataset < Sequel::Dataset
281
295
  include Sequel::Postgres::DatasetMethods
282
296
 
283
- # yield all rows returned by executing the given SQL and converting
297
+ # Yield all rows returned by executing the given SQL and converting
284
298
  # the types.
285
299
  def fetch_rows(sql)
286
- @columns = []
300
+ cols = []
287
301
  execute(sql) do |res|
288
- (0...res.ntuples).each do |recnum|
302
+ res.nfields.times do |fieldnum|
303
+ cols << [fieldnum, PG_TYPES[res.ftype(fieldnum)], res.fname(fieldnum).to_sym]
304
+ end
305
+ @columns = cols.map{|c| c.at(2)}
306
+ res.ntuples.times do |recnum|
289
307
  converted_rec = {}
290
- (0...res.nfields).each do |fieldnum|
291
- fieldsym = res.fname(fieldnum).to_sym
292
- @columns << fieldsym
293
- converted_rec[fieldsym] = if value = res.getvalue(recnum,fieldnum)
294
- (PG_TYPES[res.ftype(fieldnum)] || lambda{|s| s.to_s}).call(value)
295
- else
296
- value
297
- end
308
+ cols.each do |fieldnum, type_proc, fieldsym|
309
+ value = res.getvalue(recnum, fieldnum)
310
+ converted_rec[fieldsym] = (value && type_proc) ? type_proc.call(value) : value
298
311
  end
299
312
  yield converted_rec
300
313
  end
@@ -120,7 +120,6 @@ module Sequel
120
120
 
121
121
  sql
122
122
  end
123
- alias_method :sql, :select_sql
124
123
  end
125
124
  end
126
125
  end
@@ -156,7 +156,7 @@ module Sequel
156
156
 
157
157
  # Transforms an CROSS JOIN to an INNER JOIN if the expr is not nil.
158
158
  # Raises an error on use of :full_outer type, since MySQL doesn't support it.
159
- def join_table(type, table, expr=nil, table_alias=nil)
159
+ def join_table(type, table, expr=nil, table_alias={})
160
160
  type = :inner if (type == :cross) && !expr.nil?
161
161
  raise(Sequel::Error, "MySQL doesn't support FULL OUTER JOIN") if type == :full_outer
162
162
  super(type, table, expr, table_alias)
@@ -0,0 +1,82 @@
1
+ module Sequel
2
+ module Oracle
3
+ module DatabaseMethods
4
+ def tables
5
+ from(:tab).select(:tname).filter(:tabtype => 'TABLE').map do |r|
6
+ r[:tname].downcase.to_sym
7
+ end
8
+ end
9
+
10
+ def table_exists?(name)
11
+ from(:tab).filter(:tname => name.to_s.upcase, :tabtype => 'TABLE').count > 0
12
+ end
13
+ end
14
+
15
+ module DatasetMethods
16
+ def empty?
17
+ db[:dual].where(exists).get(1) == nil
18
+ end
19
+
20
+ # Formats a SELECT statement using the given options and the dataset
21
+ # options.
22
+ def select_sql(opts = nil)
23
+ opts = opts ? @opts.merge(opts) : @opts
24
+
25
+ if sql = opts[:sql]
26
+ return sql
27
+ end
28
+
29
+ columns = opts[:select]
30
+ select_columns = columns ? column_list(columns) : '*'
31
+ sql = opts[:distinct] ? \
32
+ "SELECT DISTINCT #{select_columns}" : \
33
+ "SELECT #{select_columns}"
34
+
35
+ if opts[:from]
36
+ sql << " FROM #{source_list(opts[:from])}"
37
+ end
38
+
39
+ if join = opts[:join]
40
+ join.each{|j| sql << literal(j)}
41
+ end
42
+
43
+ if where = opts[:where]
44
+ sql << " WHERE #{literal(where)}"
45
+ end
46
+
47
+ if group = opts[:group]
48
+ sql << " GROUP BY #{expression_list(group)}"
49
+ end
50
+
51
+ if having = opts[:having]
52
+ sql << " HAVING #{literal(having)}"
53
+ end
54
+
55
+ if union = opts[:union]
56
+ sql << (opts[:union_all] ? \
57
+ " UNION ALL #{union.sql}" : " UNION #{union.sql}")
58
+ elsif intersect = opts[:intersect]
59
+ sql << (opts[:intersect_all] ? \
60
+ " INTERSECT ALL #{intersect.sql}" : " INTERSECT #{intersect.sql}")
61
+ elsif except = opts[:except]
62
+ sql << (opts[:except_all] ? \
63
+ " EXCEPT ALL #{except.sql}" : " EXCEPT #{except.sql}")
64
+ end
65
+
66
+ if order = opts[:order]
67
+ sql << " ORDER BY #{expression_list(order)}"
68
+ end
69
+
70
+ if limit = opts[:limit]
71
+ if (offset = opts[:offset]) && (offset > 0)
72
+ sql = "SELECT * FROM (SELECT raw_sql_.*, ROWNUM raw_rnum_ FROM(#{sql}) raw_sql_ WHERE ROWNUM <= #{limit + offset}) WHERE raw_rnum_ > #{offset}"
73
+ else
74
+ sql = "SELECT * FROM (#{sql}) WHERE ROWNUM <= #{limit}"
75
+ end
76
+ end
77
+
78
+ sql
79
+ end
80
+ end
81
+ end
82
+ end