sequel 2.5.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/CHANGELOG +48 -0
  2. data/Rakefile +16 -6
  3. data/bin/sequel +0 -0
  4. data/doc/cheat_sheet.rdoc +4 -4
  5. data/doc/schema.rdoc +9 -0
  6. data/lib/sequel_core/adapters/jdbc.rb +7 -7
  7. data/lib/sequel_core/adapters/mysql.rb +6 -11
  8. data/lib/sequel_core/adapters/shared/mssql.rb +21 -1
  9. data/lib/sequel_core/adapters/shared/mysql.rb +19 -27
  10. data/lib/sequel_core/adapters/shared/postgres.rb +67 -11
  11. data/lib/sequel_core/adapters/shared/sqlite.rb +11 -22
  12. data/lib/sequel_core/adapters/sqlite.rb +8 -4
  13. data/lib/sequel_core/core_sql.rb +4 -4
  14. data/lib/sequel_core/database.rb +56 -31
  15. data/lib/sequel_core/database/schema.rb +13 -5
  16. data/lib/sequel_core/dataset/convenience.rb +1 -1
  17. data/lib/sequel_core/dataset/sql.rb +30 -15
  18. data/lib/sequel_core/migration.rb +7 -0
  19. data/lib/sequel_core/schema/generator.rb +13 -2
  20. data/lib/sequel_core/schema/sql.rb +27 -81
  21. data/lib/sequel_core/sql.rb +5 -2
  22. data/lib/sequel_model.rb +8 -2
  23. data/lib/sequel_model/associations.rb +7 -4
  24. data/lib/sequel_model/base.rb +10 -1
  25. data/lib/sequel_model/eager_loading.rb +29 -10
  26. data/lib/sequel_model/record.rb +19 -5
  27. data/spec/adapters/mysql_spec.rb +2 -2
  28. data/spec/adapters/postgres_spec.rb +26 -5
  29. data/spec/adapters/sqlite_spec.rb +42 -8
  30. data/spec/integration/eager_loader_test.rb +51 -58
  31. data/spec/integration/schema_test.rb +28 -4
  32. data/spec/sequel_core/core_sql_spec.rb +3 -0
  33. data/spec/sequel_core/database_spec.rb +24 -0
  34. data/spec/sequel_core/dataset_spec.rb +25 -17
  35. data/spec/sequel_core/schema_spec.rb +58 -26
  36. data/spec/sequel_model/eager_loading_spec.rb +65 -0
  37. data/spec/sequel_model/hooks_spec.rb +1 -1
  38. data/spec/sequel_model/model_spec.rb +1 -0
  39. data/spec/sequel_model/record_spec.rb +74 -1
  40. data/spec/sequel_model/spec_helper.rb +8 -0
  41. metadata +5 -3
@@ -8,6 +8,7 @@ module Sequel
8
8
  # Database class for PostgreSQL databases used with Sequel and the
9
9
  # ruby-sqlite3 driver.
10
10
  class Database < Sequel::Database
11
+ UNIX_EPOCH_TIME_FORMAT = /\A\d+\z/.freeze
11
12
  include ::Sequel::SQLite::DatabaseMethods
12
13
 
13
14
  set_adapter_scheme :sqlite
@@ -30,10 +31,13 @@ module Sequel
30
31
  db = ::SQLite3::Database.new(opts[:database])
31
32
  db.busy_timeout(opts.fetch(:timeout, 5000))
32
33
  db.type_translation = true
33
- # fix for timestamp translation
34
- db.translator.add_translator("timestamp") do |t, v|
35
- v =~ /^\d+$/ ? Time.at(v.to_i) : Time.parse(v)
36
- end
34
+ # Handle datetime's with Sequel.datetime_class
35
+ prok = proc do |t,v|
36
+ v = Time.at(v.to_i).iso8601 if UNIX_EPOCH_TIME_FORMAT.match(v)
37
+ v.to_sequel_time
38
+ end
39
+ db.translator.add_translator("timestamp", &prok)
40
+ db.translator.add_translator("datetime", &prok)
37
41
  db
38
42
  end
39
43
 
@@ -7,8 +7,8 @@ class Array
7
7
 
8
8
  # Return a Sequel::SQL::CaseExpression with this array as the conditions and the given
9
9
  # default value.
10
- def case(default)
11
- ::Sequel::SQL::CaseExpression.new(self, default)
10
+ def case(default, expression = nil)
11
+ ::Sequel::SQL::CaseExpression.new(self, default, expression)
12
12
  end
13
13
 
14
14
  # Return a Sequel::SQL::BooleanExpression created from this array, matching all of the
@@ -87,8 +87,8 @@ class Hash
87
87
  # Return a Sequel::SQL::CaseExpression with this hash as the conditions and the given
88
88
  # default value. Note that the order of the conditions will be arbitrary, so all
89
89
  # conditions should be orthogonal.
90
- def case(default)
91
- ::Sequel::SQL::CaseExpression.new(to_a, default)
90
+ def case(default, expression = nil)
91
+ ::Sequel::SQL::CaseExpression.new(to_a, default, expression)
92
92
  end
93
93
 
94
94
  # Return a Sequel::SQL::BooleanExpression created from this hash, matching all of the
@@ -355,7 +355,7 @@ module Sequel
355
355
  synchronize(server){|conn|}
356
356
  true
357
357
  end
358
-
358
+
359
359
  # A simple implementation of SQL transactions. Nested transactions are not
360
360
  # supported - calling #transaction within a transaction will reuse the
361
361
  # current transaction. Should be overridden for databases that support nested
@@ -363,19 +363,19 @@ module Sequel
363
363
  def transaction(server=nil)
364
364
  synchronize(server) do |conn|
365
365
  return yield(conn) if @transactions.include?(Thread.current)
366
- log_info(SQL_BEGIN)
367
- conn.execute(SQL_BEGIN)
366
+ log_info(begin_transaction_sql)
367
+ conn.execute(begin_transaction_sql)
368
368
  begin
369
369
  @transactions << Thread.current
370
370
  yield(conn)
371
371
  rescue Exception => e
372
- log_info(SQL_ROLLBACK)
373
- conn.execute(SQL_ROLLBACK)
372
+ log_info(rollback_transaction_sql)
373
+ conn.execute(rollback_transaction_sql)
374
374
  transaction_error(e)
375
375
  ensure
376
376
  unless e
377
- log_info(SQL_COMMIT)
378
- conn.execute(SQL_COMMIT)
377
+ log_info(commit_transaction_sql)
378
+ conn.execute(commit_transaction_sql)
379
379
  end
380
380
  @transactions.delete(Thread.current)
381
381
  end
@@ -384,15 +384,25 @@ module Sequel
384
384
 
385
385
  # Typecast the value to the given column_type. Can be overridden in
386
386
  # adapters to support database specific column types.
387
+ # This method should raise Sequel::Error::InvalidValue if assigned value
388
+ # is invalid.
387
389
  def typecast_value(column_type, value)
388
390
  return nil if value.nil?
389
391
  case column_type
390
392
  when :integer
391
- Integer(value)
393
+ begin
394
+ Integer(value)
395
+ rescue ArgumentError => e
396
+ raise Sequel::Error::InvalidValue, e.message.inspect
397
+ end
392
398
  when :string
393
399
  value.to_s
394
400
  when :float
395
- Float(value)
401
+ begin
402
+ Float(value)
403
+ rescue ArgumentError => e
404
+ raise Sequel::Error::InvalidValue, e.message.inspect
405
+ end
396
406
  when :decimal
397
407
  case value
398
408
  when BigDecimal
@@ -402,7 +412,7 @@ module Sequel
402
412
  when Integer
403
413
  value.to_s.to_d
404
414
  else
405
- raise ArgumentError, "invalid value for BigDecimal: #{value.inspect}"
415
+ raise Sequel::Error::InvalidValue, "invalid value for BigDecimal: #{value.inspect}"
406
416
  end
407
417
  when :boolean
408
418
  case value
@@ -420,7 +430,7 @@ module Sequel
420
430
  when String
421
431
  value.to_date
422
432
  else
423
- raise ArgumentError, "invalid value for Date: #{value.inspect}"
433
+ raise Sequel::Error::InvalidValue, "invalid value for Date: #{value.inspect}"
424
434
  end
425
435
  when :time
426
436
  case value
@@ -429,10 +439,10 @@ module Sequel
429
439
  when String
430
440
  value.to_time
431
441
  else
432
- raise ArgumentError, "invalid value for Time: #{value.inspect}"
442
+ raise Sequel::Error::InvalidValue, "invalid value for Time: #{value.inspect}"
433
443
  end
434
444
  when :datetime
435
- raise(ArgumentError, "invalid value for #{tc}: #{value.inspect}") unless value.is_one_of?(DateTime, Date, Time, String)
445
+ raise(Sequel::Error::InvalidValue, "invalid value for Datetime: #{value.inspect}") unless value.is_one_of?(DateTime, Date, Time, String)
436
446
  if Sequel.datetime_class === value
437
447
  # Already the correct class, no need to convert
438
448
  value
@@ -446,7 +456,7 @@ module Sequel
446
456
  else
447
457
  value
448
458
  end
449
- end
459
+ end
450
460
 
451
461
  # Returns the URI identifying the database.
452
462
  # This method can raise an error if the database used options
@@ -471,6 +481,38 @@ module Sequel
471
481
 
472
482
  private
473
483
 
484
+ # SQL to BEGIN a transaction.
485
+ def begin_transaction_sql
486
+ SQL_BEGIN
487
+ end
488
+
489
+ # SQL to COMMIT a transaction.
490
+ def commit_transaction_sql
491
+ SQL_COMMIT
492
+ end
493
+
494
+ # The default options for the connection pool.
495
+ def connection_pool_default_options
496
+ {}
497
+ end
498
+
499
+ # SQL to ROLLBACK a transaction.
500
+ def rollback_transaction_sql
501
+ SQL_ROLLBACK
502
+ end
503
+
504
+ # Convert the given exception to a DatabaseError, keeping message
505
+ # and traceback.
506
+ def raise_error(exception, opts={})
507
+ if !opts[:classes] || exception.is_one_of?(*opts[:classes])
508
+ e = DatabaseError.new("#{exception.class} #{exception.message}")
509
+ e.set_backtrace(exception.backtrace)
510
+ raise e
511
+ else
512
+ raise exception
513
+ end
514
+ end
515
+
474
516
  # Return the options for the given server by merging the generic
475
517
  # options for all server with the specific options for the given
476
518
  # server specified in the :servers option.
@@ -491,23 +533,6 @@ module Sequel
491
533
  opts
492
534
  end
493
535
 
494
- # The default options for the connection pool.
495
- def connection_pool_default_options
496
- {}
497
- end
498
-
499
- # Convert the given exception to a DatabaseError, keeping message
500
- # and traceback.
501
- def raise_error(exception, opts={})
502
- if !opts[:classes] || exception.is_one_of?(*opts[:classes])
503
- e = DatabaseError.new("#{exception.class} #{exception.message}")
504
- e.set_backtrace(exception.backtrace)
505
- raise e
506
- else
507
- raise exception
508
- end
509
- end
510
-
511
536
  # Raise a database error unless the exception is an Error::Rollback.
512
537
  def transaction_error(e, *classes)
513
538
  raise_error(e, :classes=>classes) unless Error::Rollback === e
@@ -40,6 +40,7 @@ module Sequel
40
40
  #
41
41
  # See Schema::AlterTableGenerator.
42
42
  def alter_table(name, generator=nil, &block)
43
+ @schemas.delete(name.to_sym) if @schemas
43
44
  generator ||= Schema::AlterTableGenerator.new(self, &block)
44
45
  alter_table_sql_list(name, generator.operations).flatten.each {|sql| execute_ddl(sql)}
45
46
  end
@@ -70,8 +71,9 @@ module Sequel
70
71
  # DB.create_or_replace_view(:cheap_items, "SELECT * FROM items WHERE price < 100")
71
72
  # DB.create_or_replace_view(:ruby_items, DB[:items].filter(:category => 'ruby'))
72
73
  def create_or_replace_view(name, source)
74
+ @schemas.delete(name.to_sym) if @schemas
73
75
  source = source.sql if source.is_a?(Dataset)
74
- execute_ddl("CREATE OR REPLACE VIEW #{name} AS #{source}")
76
+ execute_ddl("CREATE OR REPLACE VIEW #{quote_identifier(name)} AS #{source}")
75
77
  end
76
78
 
77
79
  # Creates a view based on a dataset or an SQL string:
@@ -80,7 +82,7 @@ module Sequel
80
82
  # DB.create_view(:ruby_items, DB[:items].filter(:category => 'ruby'))
81
83
  def create_view(name, source)
82
84
  source = source.sql if source.is_a?(Dataset)
83
- execute_ddl("CREATE VIEW #{name} AS #{source}")
85
+ execute_ddl("CREATE VIEW #{quote_identifier(name)} AS #{source}")
84
86
  end
85
87
 
86
88
  # Removes a column from the specified table:
@@ -106,14 +108,20 @@ module Sequel
106
108
  #
107
109
  # DB.drop_table(:posts, :comments)
108
110
  def drop_table(*names)
109
- names.each {|n| execute_ddl(drop_table_sql(n))}
111
+ names.each do |n|
112
+ @schemas.delete(n.to_sym) if @schemas
113
+ execute_ddl(drop_table_sql(n))
114
+ end
110
115
  end
111
116
 
112
117
  # Drops a view:
113
118
  #
114
119
  # DB.drop_view(:cheap_items)
115
- def drop_view(name)
116
- execute_ddl("DROP VIEW #{name}")
120
+ def drop_view(*names)
121
+ names.each do |n|
122
+ @schemas.delete(n.to_sym) if @schemas
123
+ execute_ddl("DROP VIEW #{quote_identifier(n)}")
124
+ end
117
125
  end
118
126
 
119
127
  # Renames a table:
@@ -135,7 +135,7 @@ module Sequel
135
135
  elsif args[0].is_a?(Array) && args[1].is_a?(Dataset)
136
136
  table = @opts[:from].first
137
137
  columns, dataset = *args
138
- sql = "INSERT INTO #{quote_identifier(table)} #{literal(columns)} VALUES #{literal(dataset)}"
138
+ sql = "INSERT INTO #{quote_identifier(table)} (#{identifier_list(columns)}) VALUES #{literal(dataset)}"
139
139
  return @db.transaction{execute_dui(sql)}
140
140
  else
141
141
  # we assume that an array of hashes is given
@@ -26,12 +26,17 @@ module Sequel
26
26
 
27
27
  # SQL fragment for the aliased expression
28
28
  def aliased_expression_sql(ae)
29
- "#{literal(ae.expression)} AS #{quote_identifier(ae.aliaz)}"
29
+ as_sql(literal(ae.expression), ae.aliaz)
30
30
  end
31
31
 
32
32
  # SQL fragment for specifying given CaseExpression.
33
33
  def case_expression_sql(ce)
34
- "(CASE #{ce.conditions.collect{|c,r| "WHEN #{literal(c)} THEN #{literal(r)} "}.join}ELSE #{literal(ce.default)} END)"
34
+ sql = '(CASE '
35
+ sql << "#{literal(ce.expression)} " if ce.expression
36
+ ce.conditions.collect{ |c,r|
37
+ sql << "WHEN #{literal(c)} THEN #{literal(r)} "
38
+ }
39
+ sql << "ELSE #{literal(ce.default)} END)"
35
40
  end
36
41
 
37
42
  # SQL fragment for specifying all columns in a given table.
@@ -335,8 +340,8 @@ module Sequel
335
340
  table = jc.table
336
341
  table_alias = jc.table_alias
337
342
  table_alias = nil if table == table_alias
338
- " #{join_type_sql(jc.join_type)} #{table_ref(table)}" \
339
- "#{" AS #{quote_identifier(jc.table_alias)}" if table_alias}"
343
+ tref = table_ref(table)
344
+ " #{join_type_sql(jc.join_type)} #{table_alias ? as_sql(tref, table_alias) : tref}"
340
345
  end
341
346
 
342
347
  # SQL fragment specifying a JOIN clause with ON.
@@ -393,7 +398,7 @@ module Sequel
393
398
  raise(Sequel::Error, "can't use a block if providing an array of symbols as expr") if block_given?
394
399
  SQL::JoinUsingClause.new(expr, type, table, table_alias)
395
400
  else
396
- last_alias = @opts[:last_joined_table] || first_source
401
+ last_alias = @opts[:last_joined_table] || (first_source.is_a?(Dataset) ? 't1' : first_source)
397
402
  if Hash === expr or (Array === expr and expr.all_two_pairs?)
398
403
  expr = expr.collect do |k, v|
399
404
  k = qualified_column_name(k, table_name) if k.is_a?(Symbol)
@@ -465,7 +470,7 @@ module Sequel
465
470
  when ::Sequel::SQL::Expression
466
471
  v.to_s(self)
467
472
  when Array
468
- v.all_two_pairs? ? literal(v.sql_expr) : (v.empty? ? '(NULL)' : "(#{v.collect{|i| literal(i)}.join(COMMA_SEPARATOR)})")
473
+ v.all_two_pairs? ? literal(v.sql_expr) : (v.empty? ? '(NULL)' : "(#{expression_list(v)})")
469
474
  when Hash
470
475
  literal(v.sql_expr)
471
476
  when Time, DateTime
@@ -487,9 +492,9 @@ module Sequel
487
492
  # inserting multiple records in a single SQL statement.
488
493
  def multi_insert_sql(columns, values)
489
494
  table = quote_identifier(@opts[:from].first)
490
- columns = literal(columns)
495
+ columns = identifier_list(columns)
491
496
  values.map do |r|
492
- "INSERT INTO #{table} #{columns} VALUES #{literal(r)}"
497
+ "INSERT INTO #{table} (#{columns}) VALUES #{literal(r)}"
493
498
  end
494
499
  end
495
500
 
@@ -667,7 +672,8 @@ module Sequel
667
672
  #
668
673
  def symbol_to_column_ref(sym)
669
674
  c_table, column, c_alias = split_symbol(sym)
670
- "#{"#{quote_identifier(c_table)}." if c_table}#{quote_identifier(column)}#{" AS #{quote_identifier(c_alias)}" if c_alias}"
675
+ qc = "#{"#{quote_identifier(c_table)}." if c_table}#{quote_identifier(column)}"
676
+ c_alias ? as_sql(qc, c_alias) : qc
671
677
  end
672
678
 
673
679
  # Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
@@ -724,9 +730,7 @@ module Sequel
724
730
  # get values from hash
725
731
  values = transform_save(values) if @transform
726
732
  values.map do |k, v|
727
- # convert string key into symbol
728
- k = k.to_sym if String === k
729
- "#{literal(k)} = #{literal(v)}"
733
+ "#{k.is_one_of?(String, Symbol) ? quote_identifier(k) : literal(k)} = #{literal(v)}"
730
734
  end.join(COMMA_SEPARATOR)
731
735
  else
732
736
  # copy values verbatim
@@ -750,11 +754,17 @@ module Sequel
750
754
  # Returns a table reference for use in the FROM clause. Returns an SQL subquery
751
755
  # frgament with an optional table alias.
752
756
  def to_table_reference(table_alias=nil)
753
- "(#{sql})#{" #{quote_identifier(table_alias)}" if table_alias}"
757
+ s = "(#{sql})"
758
+ table_alias ? as_sql(s, table_alias) : s
754
759
  end
755
760
 
756
761
  private
757
762
 
763
+ # SQL fragment for specifying an alias. expression should already be literalized.
764
+ def as_sql(expression, aliaz)
765
+ "#{expression} AS #{quote_identifier(aliaz)}"
766
+ end
767
+
758
768
  # Converts an array of column names into a comma seperated string of
759
769
  # column names. If the array is empty, a wildcard (*) is returned.
760
770
  def column_list(columns)
@@ -762,7 +772,7 @@ module Sequel
762
772
  WILDCARD
763
773
  else
764
774
  m = columns.map do |i|
765
- i.is_a?(Hash) ? i.map{|k, v| "#{literal(k)} AS #{quote_identifier(v)}"} : literal(i)
775
+ i.is_a?(Hash) ? i.map{|k, v| as_sql(literal(k), v)} : literal(i)
766
776
  end
767
777
  m.join(COMMA_SEPARATOR)
768
778
  end
@@ -806,6 +816,11 @@ module Sequel
806
816
  end
807
817
  end
808
818
 
819
+ # SQL fragment specifying a list of identifiers
820
+ def identifier_list(columns)
821
+ columns.map{|i| quote_identifier(i)}.join(COMMA_SEPARATOR)
822
+ end
823
+
809
824
  # SQL statement for formatting an insert statement with default values
810
825
  def insert_default_values_sql
811
826
  "INSERT INTO #{source_list(@opts[:from])} DEFAULT VALUES"
@@ -891,7 +906,7 @@ module Sequel
891
906
  when Dataset
892
907
  t.to_table_reference
893
908
  when Hash
894
- t.map {|k, v| "#{table_ref(k)} #{table_ref(v)}"}.join(COMMA_SEPARATOR)
909
+ t.map{|k, v| as_sql(table_ref(k), v)}.join(COMMA_SEPARATOR)
895
910
  when Symbol
896
911
  symbol_to_column_ref(t)
897
912
  when String
@@ -85,6 +85,13 @@ module Sequel
85
85
  # The migration files should contain one or more migration classes based
86
86
  # on Sequel::Migration.
87
87
  #
88
+ # Migrations are generally run via the sequel command line tool,
89
+ # using the -m and -M switches. The -m switch specifies the migration
90
+ # directory, and the -M switch specifies the version to which to migrate.
91
+ #
92
+ # You can apply migrations using the Migrator API, as well (this is necessary
93
+ # if you want to specify the version from which to migrate in addition to the version
94
+ # to which to migrate).
88
95
  # To apply a migration, the #apply method must be invoked with the database
89
96
  # instance, the directory of migration files and the target version. If
90
97
  # no current version is supplied, it is read from the database. The migrator
@@ -145,9 +145,8 @@ module Sequel
145
145
  index(columns, opts.merge(:type => :spatial))
146
146
  end
147
147
 
148
- # Add a unique index on the given columns to the DDL.
148
+ # Add a unique constraint on the given columns to the DDL.
149
149
  def unique(columns, opts = {})
150
- index(columns, opts.merge(:unique => true))
151
150
  @columns << {:type => :check, :constraint_type => :unique,
152
151
  :name => nil, :columns => Array(columns)}.merge(opts)
153
152
  end
@@ -200,6 +199,13 @@ module Sequel
200
199
 
201
200
  # Add a foreign key with the given name and referencing the given table
202
201
  # to the DDL for the table. See Generator#column for the available options.
202
+ #
203
+ # You can also pass an array of column names for creating composite foreign
204
+ # keys. In this case, it will assume the columns exists and will only add
205
+ # the constraint.
206
+ #
207
+ # NOTE: If you need to add a foreign key constraint to an existing column
208
+ # use the composite key syntax even if it is only one column.
203
209
  def add_foreign_key(name, table, opts = {})
204
210
  return add_composite_foreign_key(name, table, opts) if name.is_a?(Array)
205
211
  add_column(name, :integer, {:table=>table}.merge(opts))
@@ -260,6 +266,11 @@ module Sequel
260
266
  def set_column_type(name, type)
261
267
  @operations << {:op => :set_column_type, :name => name, :type => type}
262
268
  end
269
+
270
+ # Modify a column's NOT NULL constraint.
271
+ def set_column_allow_null(name, allow_null)
272
+ @operations << {:op => :set_column_null, :name => name, :null => allow_null}
273
+ end
263
274
 
264
275
  private
265
276
 
@@ -33,6 +33,8 @@ module Sequel
33
33
  "ALTER COLUMN #{quoted_name} TYPE #{op[:type]}"
34
34
  when :set_column_default
35
35
  "ALTER COLUMN #{quoted_name} SET DEFAULT #{literal(op[:default])}"
36
+ when :set_column_null
37
+ "ALTER COLUMN #{quoted_name} #{op[:null] ? 'DROP' : 'SET'} NOT NULL"
36
38
  when :add_index
37
39
  return index_definition_sql(table, op)
38
40
  when :drop_index
@@ -100,7 +102,8 @@ module Sequel
100
102
  when :unique
101
103
  sql << "UNIQUE #{literal(constraint[:columns])}"
102
104
  else
103
- sql << "CHECK #{filter_expr(constraint[:check])}"
105
+ check = constraint[:check]
106
+ sql << "CHECK #{filter_expr((check.is_a?(Array) && check.length == 1) ? check.first : check)}"
104
107
  end
105
108
  sql
106
109
  end
@@ -205,6 +208,7 @@ module Sequel
205
208
  # table_name, otherwise you may only getting the schemas for tables
206
209
  # that have been requested explicitly.
207
210
  def schema(table_name = nil, opts={})
211
+ table_name = table_name.to_sym if table_name
208
212
  if opts[:reload] && @schemas
209
213
  if table_name
210
214
  @schemas.delete(table_name)
@@ -213,17 +217,30 @@ module Sequel
213
217
  end
214
218
  end
215
219
 
216
- if table_name
217
- return @schemas[table_name] if @schemas && @schemas[table_name]
218
- else
219
- return @schemas if @schemas
220
+ if @schemas
221
+ if table_name
222
+ return @schemas[table_name] if @schemas[table_name]
223
+ else
224
+ return @schemas
225
+ end
220
226
  end
221
227
 
222
228
  if table_name
223
229
  @schemas ||= {}
224
- @schemas[table_name] ||= schema_parse_table(table_name, opts)
230
+ if respond_to?(:schema_parse_table, true)
231
+ @schemas[table_name] ||= schema_parse_table(table_name, opts)
232
+ else
233
+ raise Error, 'schema parsing is not implemented on this database'
234
+ end
225
235
  else
226
- @schemas = schema_parse_tables(opts)
236
+ if respond_to?(:schema_parse_tables, true)
237
+ @schemas = schema_parse_tables(opts)
238
+ elsif respond_to?(:schema_parse_table, true) and respond_to?(:tables, true)
239
+ tables.each{|t| schema(t, opts)}
240
+ @schemas
241
+ else
242
+ raise Error, 'schema parsing is not implemented on this database'
243
+ end
227
244
  end
228
245
  end
229
246
 
@@ -240,11 +257,11 @@ module Sequel
240
257
  # integer, string, date, datetime, boolean, and float.
241
258
  def schema_column_type(db_type)
242
259
  case db_type
243
- when 'tinyint'
260
+ when /\Atinyint/
244
261
  Sequel.convert_tinyint_to_bool ? :boolean : :integer
245
- when /\A(int(eger)?|bigint|smallint)\z/
262
+ when /\A(int(eger)?|bigint|smallint)/
246
263
  :integer
247
- when /\A(character( varying)?|varchar|text)\z/
264
+ when /\A(character( varying)?|varchar|text)/
248
265
  :string
249
266
  when /\Adate\z/
250
267
  :date
@@ -263,77 +280,6 @@ module Sequel
263
280
  end
264
281
  end
265
282
 
266
- # The final dataset used by the schema parser, after all
267
- # options have been applied.
268
- def schema_ds(table_name, opts)
269
- schema_ds_dataset.from(*schema_ds_from(table_name, opts)) \
270
- .select(*schema_ds_select(table_name, opts)) \
271
- .join(*schema_ds_join(table_name, opts)) \
272
- .filter(*schema_ds_filter(table_name, opts))
273
- end
274
-
275
- # The blank dataset used by the schema parser.
276
- def schema_ds_dataset
277
- schema_utility_dataset
278
- end
279
-
280
- # Argument array for the schema dataset's filter method.
281
- def schema_ds_filter(table_name, opts)
282
- if table_name
283
- [{:c__table_name=>table_name.to_s}]
284
- else
285
- [{:t__table_type=>'BASE TABLE'}]
286
- end
287
- end
288
-
289
- # Argument array for the schema dataset's from method.
290
- def schema_ds_from(table_name, opts)
291
- [:information_schema__tables___t]
292
- end
293
-
294
- # Argument array for the schema dataset's join method.
295
- def schema_ds_join(table_name, opts)
296
- [:information_schema__columns, [:table_catalog, :table_schema, :table_name], :c]
297
- end
298
-
299
- # Argument array for the schema dataset's select method.
300
- def schema_ds_select(table_name, opts)
301
- cols = [:column_name___column, :data_type___db_type, :character_maximum_length___max_chars, \
302
- :numeric_precision, :column_default___default, :is_nullable___allow_null]
303
- cols << :c__table_name unless table_name
304
- cols
305
- end
306
-
307
- # Parse the schema for a given table.
308
- def schema_parse_table(table_name, opts)
309
- schema_parse_rows(schema_ds(table_name, opts))
310
- end
311
-
312
- # Parse the schema all tables in the database.
313
- def schema_parse_tables(opts)
314
- schemas = {}
315
- schema_ds(nil, opts).each do |row|
316
- (schemas[row.delete(:table_name).to_sym] ||= []) << row
317
- end
318
- schemas.each do |table, rows|
319
- schemas[table] = schema_parse_rows(rows)
320
- end
321
- schemas
322
- end
323
-
324
- # Parse the output of the information schema columns into
325
- # the hash used by Sequel.
326
- def schema_parse_rows(rows)
327
- schema = []
328
- rows.each do |row|
329
- row[:allow_null] = row[:allow_null] == 'YES' ? true : false
330
- row[:default] = nil if row[:default].blank?
331
- row[:type] = schema_column_type(row[:db_type])
332
- schema << [row.delete(:column).to_sym, row]
333
- end
334
- schema
335
- end
336
-
337
283
  # SQL fragment specifying the type of a given column.
338
284
  def type_literal(column)
339
285
  column[:size] ||= 255 if column[:type] == :varchar