sequel 2.5.0 → 2.6.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 (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