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.
- data/CHANGELOG +48 -0
- data/Rakefile +16 -6
- data/bin/sequel +0 -0
- data/doc/cheat_sheet.rdoc +4 -4
- data/doc/schema.rdoc +9 -0
- data/lib/sequel_core/adapters/jdbc.rb +7 -7
- data/lib/sequel_core/adapters/mysql.rb +6 -11
- data/lib/sequel_core/adapters/shared/mssql.rb +21 -1
- data/lib/sequel_core/adapters/shared/mysql.rb +19 -27
- data/lib/sequel_core/adapters/shared/postgres.rb +67 -11
- data/lib/sequel_core/adapters/shared/sqlite.rb +11 -22
- data/lib/sequel_core/adapters/sqlite.rb +8 -4
- data/lib/sequel_core/core_sql.rb +4 -4
- data/lib/sequel_core/database.rb +56 -31
- data/lib/sequel_core/database/schema.rb +13 -5
- data/lib/sequel_core/dataset/convenience.rb +1 -1
- data/lib/sequel_core/dataset/sql.rb +30 -15
- data/lib/sequel_core/migration.rb +7 -0
- data/lib/sequel_core/schema/generator.rb +13 -2
- data/lib/sequel_core/schema/sql.rb +27 -81
- data/lib/sequel_core/sql.rb +5 -2
- data/lib/sequel_model.rb +8 -2
- data/lib/sequel_model/associations.rb +7 -4
- data/lib/sequel_model/base.rb +10 -1
- data/lib/sequel_model/eager_loading.rb +29 -10
- data/lib/sequel_model/record.rb +19 -5
- data/spec/adapters/mysql_spec.rb +2 -2
- data/spec/adapters/postgres_spec.rb +26 -5
- data/spec/adapters/sqlite_spec.rb +42 -8
- data/spec/integration/eager_loader_test.rb +51 -58
- data/spec/integration/schema_test.rb +28 -4
- data/spec/sequel_core/core_sql_spec.rb +3 -0
- data/spec/sequel_core/database_spec.rb +24 -0
- data/spec/sequel_core/dataset_spec.rb +25 -17
- data/spec/sequel_core/schema_spec.rb +58 -26
- data/spec/sequel_model/eager_loading_spec.rb +65 -0
- data/spec/sequel_model/hooks_spec.rb +1 -1
- data/spec/sequel_model/model_spec.rb +1 -0
- data/spec/sequel_model/record_spec.rb +74 -1
- data/spec/sequel_model/spec_helper.rb +8 -0
- 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
|
-
#
|
34
|
-
|
35
|
-
v
|
36
|
-
|
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
|
|
data/lib/sequel_core/core_sql.rb
CHANGED
@@ -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
|
data/lib/sequel_core/database.rb
CHANGED
@@ -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(
|
367
|
-
conn.execute(
|
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(
|
373
|
-
conn.execute(
|
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(
|
378
|
-
conn.execute(
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
442
|
+
raise Sequel::Error::InvalidValue, "invalid value for Time: #{value.inspect}"
|
433
443
|
end
|
434
444
|
when :datetime
|
435
|
-
raise(
|
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
|
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(
|
116
|
-
|
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)} #{
|
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
|
-
|
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
|
-
|
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
|
-
|
339
|
-
|
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
|
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 =
|
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)}
|
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
|
-
#
|
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})
|
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|
|
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
|
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
|
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
|
-
|
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
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
-
|
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
|
-
|
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
|
260
|
+
when /\Atinyint/
|
244
261
|
Sequel.convert_tinyint_to_bool ? :boolean : :integer
|
245
|
-
when /\A(int(eger)?|bigint|smallint)
|
262
|
+
when /\A(int(eger)?|bigint|smallint)/
|
246
263
|
:integer
|
247
|
-
when /\A(character( varying)?|varchar|text)
|
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
|