sequel 2.5.0 → 2.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|