sequel 2.9.0 → 2.10.0

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 (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