sequel 2.9.0 → 2.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/CHANGELOG +56 -0
  2. data/{README → README.rdoc} +85 -57
  3. data/Rakefile +10 -5
  4. data/bin/sequel +7 -16
  5. data/doc/advanced_associations.rdoc +5 -17
  6. data/doc/cheat_sheet.rdoc +18 -20
  7. data/doc/dataset_filtering.rdoc +8 -32
  8. data/doc/schema.rdoc +20 -0
  9. data/lib/sequel_core.rb +35 -1
  10. data/lib/sequel_core/adapters/ado.rb +1 -1
  11. data/lib/sequel_core/adapters/db2.rb +2 -2
  12. data/lib/sequel_core/adapters/dbi.rb +2 -11
  13. data/lib/sequel_core/adapters/do.rb +205 -0
  14. data/lib/sequel_core/adapters/do/mysql.rb +38 -0
  15. data/lib/sequel_core/adapters/do/postgres.rb +92 -0
  16. data/lib/sequel_core/adapters/do/sqlite.rb +31 -0
  17. data/lib/sequel_core/adapters/firebird.rb +298 -0
  18. data/lib/sequel_core/adapters/informix.rb +10 -1
  19. data/lib/sequel_core/adapters/jdbc.rb +78 -19
  20. data/lib/sequel_core/adapters/jdbc/h2.rb +69 -0
  21. data/lib/sequel_core/adapters/jdbc/mysql.rb +10 -0
  22. data/lib/sequel_core/adapters/jdbc/postgresql.rb +7 -3
  23. data/lib/sequel_core/adapters/mysql.rb +53 -77
  24. data/lib/sequel_core/adapters/odbc.rb +1 -1
  25. data/lib/sequel_core/adapters/openbase.rb +1 -1
  26. data/lib/sequel_core/adapters/oracle.rb +2 -2
  27. data/lib/sequel_core/adapters/postgres.rb +16 -14
  28. data/lib/sequel_core/adapters/shared/mysql.rb +44 -9
  29. data/lib/sequel_core/adapters/shared/oracle.rb +4 -5
  30. data/lib/sequel_core/adapters/shared/postgres.rb +86 -82
  31. data/lib/sequel_core/adapters/shared/sqlite.rb +39 -24
  32. data/lib/sequel_core/adapters/sqlite.rb +11 -1
  33. data/lib/sequel_core/connection_pool.rb +10 -2
  34. data/lib/sequel_core/core_sql.rb +13 -3
  35. data/lib/sequel_core/database.rb +131 -30
  36. data/lib/sequel_core/database/schema.rb +5 -5
  37. data/lib/sequel_core/dataset.rb +31 -6
  38. data/lib/sequel_core/dataset/convenience.rb +11 -11
  39. data/lib/sequel_core/dataset/query.rb +2 -2
  40. data/lib/sequel_core/dataset/sql.rb +6 -6
  41. data/lib/sequel_core/exceptions.rb +4 -0
  42. data/lib/sequel_core/migration.rb +4 -4
  43. data/lib/sequel_core/schema/generator.rb +19 -3
  44. data/lib/sequel_core/schema/sql.rb +24 -20
  45. data/lib/sequel_core/sql.rb +13 -16
  46. data/lib/sequel_core/version.rb +11 -0
  47. data/lib/sequel_model.rb +2 -0
  48. data/lib/sequel_model/base.rb +2 -2
  49. data/lib/sequel_model/hooks.rb +46 -7
  50. data/lib/sequel_model/record.rb +11 -9
  51. data/lib/sequel_model/schema.rb +1 -1
  52. data/lib/sequel_model/validations.rb +72 -61
  53. data/spec/adapters/firebird_spec.rb +371 -0
  54. data/spec/adapters/mysql_spec.rb +118 -62
  55. data/spec/adapters/oracle_spec.rb +5 -5
  56. data/spec/adapters/postgres_spec.rb +33 -18
  57. data/spec/adapters/sqlite_spec.rb +2 -2
  58. data/spec/integration/dataset_test.rb +3 -3
  59. data/spec/integration/schema_test.rb +55 -5
  60. data/spec/integration/spec_helper.rb +11 -0
  61. data/spec/integration/type_test.rb +59 -16
  62. data/spec/sequel_core/connection_pool_spec.rb +14 -0
  63. data/spec/sequel_core/core_sql_spec.rb +24 -14
  64. data/spec/sequel_core/database_spec.rb +96 -11
  65. data/spec/sequel_core/dataset_spec.rb +97 -37
  66. data/spec/sequel_core/expression_filters_spec.rb +51 -40
  67. data/spec/sequel_core/object_graph_spec.rb +2 -2
  68. data/spec/sequel_core/schema_generator_spec.rb +31 -6
  69. data/spec/sequel_core/schema_spec.rb +25 -9
  70. data/spec/sequel_core/spec_helper.rb +4 -1
  71. data/spec/sequel_core/version_spec.rb +7 -0
  72. data/spec/sequel_model/associations_spec.rb +1 -1
  73. data/spec/sequel_model/hooks_spec.rb +68 -13
  74. data/spec/sequel_model/model_spec.rb +4 -4
  75. data/spec/sequel_model/record_spec.rb +22 -0
  76. data/spec/sequel_model/spec_helper.rb +2 -1
  77. data/spec/sequel_model/validations_spec.rb +107 -7
  78. metadata +15 -5
@@ -0,0 +1,38 @@
1
+ require 'sequel_core/adapters/shared/mysql'
2
+
3
+ module Sequel
4
+ module DataObjects
5
+ # Database and Dataset instance methods for MySQL specific
6
+ # support via DataObjects.
7
+ module MySQL
8
+ # Database instance methods for MySQL databases accessed via DataObjects.
9
+ module DatabaseMethods
10
+ include Sequel::MySQL::DatabaseMethods
11
+
12
+ # Return instance of Sequel::DataObjects::MySQL::Dataset with the given opts.
13
+ def dataset(opts=nil)
14
+ Sequel::DataObjects::MySQL::Dataset.new(self, opts)
15
+ end
16
+
17
+ private
18
+
19
+ # The database name for the given database. Need to parse it out
20
+ # of the connection string, since the DataObjects does no parsing on the
21
+ # given connection string by default.
22
+ def database_name
23
+ (m = /\/(.*)/.match(URI.parse(uri).path)) && m[1]
24
+ end
25
+ end
26
+
27
+ # Dataset class for MySQL datasets accessed via DataObjects.
28
+ class Dataset < DataObjects::Dataset
29
+ include Sequel::MySQL::DatasetMethods
30
+
31
+ # Use execute_insert to execute the replace_sql.
32
+ def replace(*args)
33
+ execute_insert(replace_sql(*args))
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,92 @@
1
+ require 'sequel_core/adapters/shared/postgres'
2
+
3
+ module Sequel
4
+ Postgres::CONVERTED_EXCEPTIONS << PostgresError
5
+
6
+ module DataObjects
7
+ # Adapter, Database, and Dataset support for accessing a PostgreSQL
8
+ # database via DataObjects.
9
+ module Postgres
10
+ # Methods to add to the DataObjects adapter/connection to allow it to work
11
+ # with the shared PostgreSQL code.
12
+ module AdapterMethods
13
+ include Sequel::Postgres::AdapterMethods
14
+
15
+ # Give the DataObjects adapter a direct execute method, which creates
16
+ # a statement with the given sql and executes it.
17
+ def execute(sql, args=nil)
18
+ command = create_command(sql)
19
+ begin
20
+ if block_given?
21
+ begin
22
+ reader = command.execute_reader
23
+ yield(reader)
24
+ ensure
25
+ reader.close if reader
26
+ end
27
+ else
28
+ command.execute_non_query
29
+ end
30
+ rescue PostgresError => e
31
+ raise_error(e)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ # DataObjects specific method of getting specific values from a result set.
38
+ def single_value(reader)
39
+ while(reader.next!) do
40
+ return reader.values.at(0)
41
+ end
42
+ end
43
+ end
44
+
45
+ # Methods to add to Database instances that access PostgreSQL via
46
+ # DataObjects.
47
+ module DatabaseMethods
48
+ include Sequel::Postgres::DatabaseMethods
49
+
50
+ # Add the primary_keys and primary_key_sequences instance variables,
51
+ # so we can get the correct return values for inserted rows.
52
+ def self.extended(db)
53
+ db.instance_eval do
54
+ @primary_keys = {}
55
+ @primary_key_sequences = {}
56
+ end
57
+ end
58
+
59
+ # Return instance of Sequel::DataObjects::Postgres::Dataset with the given opts.
60
+ def dataset(opts=nil)
61
+ Sequel::DataObjects::Postgres::Dataset.new(self, opts)
62
+ end
63
+
64
+ # Run the INSERT sql on the database and return the primary key
65
+ # for the record.
66
+ def execute_insert(sql, opts={})
67
+ log_info(sql)
68
+ synchronize(opts[:server]) do |conn|
69
+ conn.create_command(sql).execute_non_query
70
+ insert_result(conn, opts[:table], opts[:values])
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ # Extend the adapter with the DataObjects PostgreSQL AdapterMethods
77
+ def setup_connection(conn)
78
+ conn = super(conn)
79
+ conn.extend(Sequel::DataObjects::Postgres::AdapterMethods)
80
+ conn.db = self
81
+ conn.apply_connection_settings
82
+ conn
83
+ end
84
+ end
85
+
86
+ # Dataset subclass used for datasets that connect to PostgreSQL via DataObjects.
87
+ class Dataset < DataObjects::Dataset
88
+ include Sequel::Postgres::DatasetMethods
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,31 @@
1
+ require 'sequel_core/adapters/shared/sqlite'
2
+
3
+ module Sequel
4
+ module DataObjects
5
+ # Database and Dataset support for SQLite databases accessed via DataObjects.
6
+ module SQLite
7
+ # Instance methods for SQLite Database objects accessed via DataObjects.
8
+ module DatabaseMethods
9
+ include Sequel::SQLite::DatabaseMethods
10
+
11
+ # Return Sequel::DataObjects::SQLite::Dataset object with the given opts.
12
+ def dataset(opts=nil)
13
+ Sequel::DataObjects::SQLite::Dataset.new(self, opts)
14
+ end
15
+
16
+ private
17
+
18
+ # Default to a single connection for a memory database.
19
+ def connection_pool_default_options
20
+ o = super
21
+ uri == 'sqlite3::memory:' ? o.merge(:max_connections=>1) : o
22
+ end
23
+ end
24
+
25
+ # Dataset class for SQLite datasets accessed via DataObjects.
26
+ class Dataset < DataObjects::Dataset
27
+ include Sequel::SQLite::DatasetMethods
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,298 @@
1
+ require 'fb'
2
+
3
+ module Sequel
4
+ # The Sequel Firebird adapter requires the ruby fb driver located at
5
+ # http://github.com/wishdev/fb.
6
+ module Firebird
7
+ class Database < Sequel::Database
8
+ set_adapter_scheme :firebird
9
+
10
+ AUTO_INCREMENT = ''.freeze
11
+
12
+ # Add the primary_keys and primary_key_sequences instance variables,
13
+ # so we can get the correct return values for inserted rows.
14
+ def initialize(*args)
15
+ super
16
+ @primary_keys = {}
17
+ @primary_key_sequences = {}
18
+ end
19
+
20
+ # Use Firebird specific syntax for add column
21
+ def alter_table_sql(table, op)
22
+ case op[:op]
23
+ when :add_column
24
+ "ALTER TABLE #{quote_schema_table(table)} ADD #{column_definition_sql(op)}"
25
+ when :drop_column
26
+ "ALTER TABLE #{quote_schema_table(table)} DROP #{column_definition_sql(op)}"
27
+ when :rename_column
28
+ "ALTER TABLE #{quote_schema_table(table)} ALTER #{quote_identifier(op[:name])} TO #{quote_identifier(op[:new_name])}"
29
+ when :set_column_type
30
+ "ALTER TABLE #{quote_schema_table(table)} ALTER #{quote_identifier(op[:name])} TYPE #{type_literal(op)}"
31
+ else
32
+ super(table, op)
33
+ end
34
+ end
35
+
36
+ def auto_increment_sql()
37
+ AUTO_INCREMENT
38
+ end
39
+
40
+ def connect(server)
41
+ opts = server_opts(server)
42
+
43
+ db = Fb::Database.new(
44
+ :database => "#{opts[:host]}:#{opts[:database]}",
45
+ :username => opts[:user],
46
+ :password => opts[:password])
47
+ conn = db.connect
48
+ conn.downcase_names = true
49
+ conn
50
+ end
51
+
52
+ def create_sequence_sql(name, opts={})
53
+ "CREATE SEQUENCE #{quote_identifier(name)}"
54
+ end
55
+
56
+ # Creates a table with the columns given in the provided block:
57
+ #
58
+ # DB.create_table :posts do
59
+ # primary_key :id, :serial
60
+ # column :title, :text
61
+ # column :content, :text
62
+ # index :title
63
+ # end
64
+ #
65
+ # See Schema::Generator.
66
+ # Firebird gets an override because of the mess of creating a
67
+ # generator for auto-incrementing primary keys.
68
+ def create_table(name, options={}, &block)
69
+ options = {:generator=>options} if options.is_a?(Schema::Generator)
70
+ statements = create_table_sql_list(name, *((options[:generator] ||= Schema::Generator.new(self, &block)).create_info << options))
71
+ begin
72
+ execute_ddl(statements[1])
73
+ rescue
74
+ nil
75
+ end if statements[1]
76
+ statements[0].flatten.each {|sql| execute_ddl(sql)}
77
+ end
78
+
79
+ def create_table_sql_list(name, columns, indexes = nil, options={})
80
+ statements = super
81
+ drop_seq_statement = nil
82
+ columns.each do |c|
83
+ if c[:auto_increment]
84
+ c[:sequence_name] ||= "seq_#{name}_#{c[:name]}"
85
+ unless c[:create_sequence] == false
86
+ drop_seq_statement = drop_sequence_sql(c[:sequence_name])
87
+ statements << create_sequence_sql(c[:sequence_name])
88
+ statements << restart_sequence_sql(c[:sequence_name], {:restart_position => c[:sequence_start_position]}) if c[:sequence_start_position]
89
+ end
90
+ unless c[:create_trigger] == false
91
+ c[:trigger_name] ||= "BI_#{name}_#{c[:name]}"
92
+ c[:quoted_name] = quote_identifier(c[:name])
93
+ trigger_definition = <<-END
94
+ begin
95
+ if ((new.#{c[:quoted_name]} is null) or (new.#{c[:quoted_name]} = 0)) then
96
+ begin
97
+ new.#{c[:quoted_name]} = next value for #{c[:sequence_name]};
98
+ end
99
+ end
100
+ END
101
+ statements << create_trigger_sql(name, c[:trigger_name], trigger_definition, {:events => [:insert]})
102
+ end
103
+ end
104
+ end
105
+ [statements, drop_seq_statement]
106
+ end
107
+
108
+ def create_trigger(*args)
109
+ self << create_trigger_sql(*args)
110
+ end
111
+
112
+ def create_trigger_sql(table, name, definition, opts={})
113
+ events = opts[:events] ? Array(opts[:events]) : [:insert, :update, :delete]
114
+ whence = opts[:after] ? 'AFTER' : 'BEFORE'
115
+ inactive = opts[:inactive] ? 'INACTIVE' : 'ACTIVE'
116
+ position = opts[:position] ? opts[:position] : 0
117
+ sql = <<-end_sql
118
+ CREATE TRIGGER #{quote_identifier(name)} for #{quote_identifier(table)}
119
+ #{inactive} #{whence} #{events.map{|e| e.to_s.upcase}.join(' OR ')} position #{position}
120
+ as #{definition}
121
+ end_sql
122
+ sql
123
+ end
124
+
125
+ def dataset(opts = nil)
126
+ Firebird::Dataset.new(self, opts)
127
+ end
128
+
129
+ def drop_sequence(name)
130
+ self << drop_sequence_sql(name)
131
+ end
132
+
133
+ def drop_sequence_sql(name)
134
+ "DROP SEQUENCE #{quote_identifier(name)}"
135
+ end
136
+
137
+ def execute(sql, opts={})
138
+ log_info(sql)
139
+ begin
140
+ synchronize(opts[:server]) do |conn|
141
+ r = conn.execute(sql)
142
+ yield(r) if block_given?
143
+ r
144
+ end
145
+ rescue => e
146
+ log_info(e.message)
147
+ raise_error(e, :classes=>[Fb::Error])
148
+ end
149
+ end
150
+
151
+ # Return primary key for the given table.
152
+ def primary_key(table, server=nil)
153
+ synchronize(server){|conn| primary_key_for_table(conn, table)}
154
+ end
155
+
156
+ # Returns primary key for the given table. This information is
157
+ # cached, and if the primary key for a table is changed, the
158
+ # @primary_keys instance variable should be reset manually.
159
+ def primary_key_for_table(conn, table)
160
+ @primary_keys[quote_identifier(table)] ||= conn.table_primary_key(quote_identifier(table))
161
+ end
162
+
163
+ def restart_sequence(*args)
164
+ self << restart_sequence_sql(*args)
165
+ end
166
+
167
+ def restart_sequence_sql(name, opts={})
168
+ seq_name = quote_identifier(name)
169
+ "ALTER SEQUENCE #{seq_name} RESTART WITH #{opts[:restart_position]}"
170
+ end
171
+
172
+ def sequences(opts={})
173
+ ds = self[:"rdb$generators"].server(opts[:server]).filter(:"rdb$system_flag" => 0).select(:"rdb$generator_name")
174
+ block_given? ? yield(ds) : ds.map{|r| ds.send(:output_identifier, r[:"rdb$generator_name"])}
175
+ end
176
+
177
+ def tables(opts={})
178
+ ds = self[:"rdb$relations"].server(opts[:server]).filter(:"rdb$view_blr" => nil, Sequel::SQL::Function.new(:COALESCE, :"rdb$system_flag", 0) => 0).select(:"rdb$relation_name")
179
+ block_given? ? yield(ds) : ds.map{|r| ds.send(:output_identifier, r[:"rdb$relation_name"])}
180
+ end
181
+
182
+ def transaction(server=nil)
183
+ synchronize(server) do |conn|
184
+ return yield(conn) if @transactions.include?(Thread.current)
185
+ log_info("Transaction.begin")
186
+ conn.transaction
187
+ begin
188
+ @transactions << Thread.current
189
+ yield(conn)
190
+ rescue ::Exception => e
191
+ log_info("Transaction.rollback")
192
+ conn.rollback
193
+ transaction_error(e, Fb::Error)
194
+ ensure
195
+ unless e
196
+ log_info("Transaction.commit")
197
+ conn.commit
198
+ end
199
+ @transactions.delete(Thread.current)
200
+ end
201
+ end
202
+ end
203
+
204
+ private
205
+
206
+ def disconnect_connection(c)
207
+ c.close
208
+ end
209
+ end
210
+
211
+ # Dataset class for Firebird datasets
212
+ class Dataset < Sequel::Dataset
213
+ include UnsupportedIntersectExcept
214
+
215
+ BOOL_TRUE = '1'.freeze
216
+ BOOL_FALSE = '0'.freeze
217
+ COMMA_SEPARATOR = ', '.freeze
218
+ FIREBIRD_TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S".freeze
219
+ SELECT_CLAUSE_ORDER = %w'distinct limit columns from join where group having compounds order'.freeze
220
+
221
+ # Yield all rows returned by executing the given SQL and converting
222
+ # the types.
223
+ def fetch_rows(sql, &block)
224
+ execute(sql) do |s|
225
+ begin
226
+ @columns = s.fields.map{|c| output_identifier(c.name)}
227
+ s.fetchall(:symbols_hash).each do |r|
228
+ h = {}
229
+ r.each{|k,v| h[output_identifier(k)] = v}
230
+ yield h
231
+ end
232
+ ensure
233
+ s.close
234
+ end
235
+ end
236
+ self
237
+ end
238
+
239
+ # Insert given values into the database.
240
+ def insert(*values)
241
+ if !@opts[:sql]
242
+ single_value(:sql=>insert_returning_pk_sql(*values))
243
+ else
244
+ execute_insert(insert_sql(*values), :table=>opts[:from].first,
245
+ :values=>values.size == 1 ? values.first : values)
246
+ end
247
+ end
248
+
249
+ # Use the RETURNING clause to return the primary key of the inserted record, if it exists
250
+ def insert_returning_pk_sql(*values)
251
+ pk = db.primary_key(opts[:from].first)
252
+ insert_returning_sql(pk ? Sequel::SQL::Identifier.new(pk) : 'NULL'.lit, *values)
253
+ end
254
+
255
+ # Use the RETURNING clause to return the columns listed in returning.
256
+ def insert_returning_sql(returning, *values)
257
+ "#{insert_sql(*values)} RETURNING #{column_list(Array(returning))}"
258
+ end
259
+
260
+ # Insert a record returning the record inserted
261
+ def insert_select(*values)
262
+ single_record(:naked=>true, :sql=>insert_returning_sql(nil, *values))
263
+ end
264
+
265
+ def literal(v)
266
+ case v
267
+ when Time, DateTime
268
+ "#{v.strftime(FIREBIRD_TIMESTAMP_FORMAT)}.#{sprintf("%04d",v.usec / 100)}'"
269
+ when TrueClass
270
+ BOOL_TRUE
271
+ when FalseClass
272
+ BOOL_FALSE
273
+ else
274
+ super
275
+ end
276
+ end
277
+
278
+ # The order of clauses in the SELECT SQL statement
279
+ def select_clause_order
280
+ SELECT_CLAUSE_ORDER
281
+ end
282
+
283
+ def select_limit_sql(sql, opts)
284
+ sql << " FIRST #{opts[:limit]}" if opts[:limit]
285
+ sql << " SKIP #{opts[:offset]}" if opts[:offset]
286
+ end
287
+
288
+ private
289
+
290
+ def hash_row(stmt, row)
291
+ @columns.inject({}) do |m, c|
292
+ m[c] = row.shift
293
+ m
294
+ end
295
+ end
296
+ end
297
+ end
298
+ end