sequel 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +1551 -4
- data/README +306 -19
- data/Rakefile +84 -56
- data/bin/sequel +106 -0
- data/doc/cheat_sheet.rdoc +225 -0
- data/doc/dataset_filtering.rdoc +182 -0
- data/lib/sequel_core.rb +136 -0
- data/lib/sequel_core/adapters/adapter_skeleton.rb +54 -0
- data/lib/sequel_core/adapters/ado.rb +80 -0
- data/lib/sequel_core/adapters/db2.rb +148 -0
- data/lib/sequel_core/adapters/dbi.rb +117 -0
- data/lib/sequel_core/adapters/informix.rb +78 -0
- data/lib/sequel_core/adapters/jdbc.rb +186 -0
- data/lib/sequel_core/adapters/jdbc/mysql.rb +55 -0
- data/lib/sequel_core/adapters/jdbc/postgresql.rb +66 -0
- data/lib/sequel_core/adapters/jdbc/sqlite.rb +47 -0
- data/lib/sequel_core/adapters/mysql.rb +231 -0
- data/lib/sequel_core/adapters/odbc.rb +155 -0
- data/lib/sequel_core/adapters/odbc_mssql.rb +106 -0
- data/lib/sequel_core/adapters/openbase.rb +64 -0
- data/lib/sequel_core/adapters/oracle.rb +170 -0
- data/lib/sequel_core/adapters/postgres.rb +199 -0
- data/lib/sequel_core/adapters/shared/mysql.rb +275 -0
- data/lib/sequel_core/adapters/shared/postgres.rb +351 -0
- data/lib/sequel_core/adapters/shared/sqlite.rb +146 -0
- data/lib/sequel_core/adapters/sqlite.rb +138 -0
- data/lib/sequel_core/connection_pool.rb +194 -0
- data/lib/sequel_core/core_ext.rb +203 -0
- data/lib/sequel_core/core_sql.rb +184 -0
- data/lib/sequel_core/database.rb +471 -0
- data/lib/sequel_core/database/schema.rb +156 -0
- data/lib/sequel_core/dataset.rb +457 -0
- data/lib/sequel_core/dataset/callback.rb +13 -0
- data/lib/sequel_core/dataset/convenience.rb +245 -0
- data/lib/sequel_core/dataset/pagination.rb +96 -0
- data/lib/sequel_core/dataset/query.rb +41 -0
- data/lib/sequel_core/dataset/schema.rb +15 -0
- data/lib/sequel_core/dataset/sql.rb +889 -0
- data/lib/sequel_core/deprecated.rb +26 -0
- data/lib/sequel_core/exceptions.rb +42 -0
- data/lib/sequel_core/migration.rb +187 -0
- data/lib/sequel_core/object_graph.rb +216 -0
- data/lib/sequel_core/pretty_table.rb +71 -0
- data/lib/sequel_core/schema.rb +2 -0
- data/lib/sequel_core/schema/generator.rb +239 -0
- data/lib/sequel_core/schema/sql.rb +325 -0
- data/lib/sequel_core/sql.rb +812 -0
- data/lib/sequel_model.rb +5 -1
- data/lib/sequel_model/association_reflection.rb +3 -8
- data/lib/sequel_model/base.rb +15 -10
- data/lib/sequel_model/inflector.rb +3 -5
- data/lib/sequel_model/plugins.rb +1 -1
- data/lib/sequel_model/record.rb +11 -3
- data/lib/sequel_model/schema.rb +4 -4
- data/lib/sequel_model/validations.rb +6 -1
- data/spec/adapters/ado_spec.rb +17 -0
- data/spec/adapters/informix_spec.rb +96 -0
- data/spec/adapters/mysql_spec.rb +764 -0
- data/spec/adapters/oracle_spec.rb +222 -0
- data/spec/adapters/postgres_spec.rb +441 -0
- data/spec/adapters/spec_helper.rb +7 -0
- data/spec/adapters/sqlite_spec.rb +400 -0
- data/spec/integration/dataset_test.rb +51 -0
- data/spec/integration/eager_loader_test.rb +702 -0
- data/spec/integration/schema_test.rb +102 -0
- data/spec/integration/spec_helper.rb +44 -0
- data/spec/integration/type_test.rb +43 -0
- data/spec/rcov.opts +2 -0
- data/spec/sequel_core/connection_pool_spec.rb +363 -0
- data/spec/sequel_core/core_ext_spec.rb +156 -0
- data/spec/sequel_core/core_sql_spec.rb +427 -0
- data/spec/sequel_core/database_spec.rb +964 -0
- data/spec/sequel_core/dataset_spec.rb +2977 -0
- data/spec/sequel_core/expression_filters_spec.rb +346 -0
- data/spec/sequel_core/migration_spec.rb +261 -0
- data/spec/sequel_core/object_graph_spec.rb +234 -0
- data/spec/sequel_core/pretty_table_spec.rb +58 -0
- data/spec/sequel_core/schema_generator_spec.rb +122 -0
- data/spec/sequel_core/schema_spec.rb +497 -0
- data/spec/sequel_core/spec_helper.rb +51 -0
- data/spec/{association_reflection_spec.rb → sequel_model/association_reflection_spec.rb} +6 -6
- data/spec/{associations_spec.rb → sequel_model/associations_spec.rb} +47 -18
- data/spec/{base_spec.rb → sequel_model/base_spec.rb} +2 -1
- data/spec/{caching_spec.rb → sequel_model/caching_spec.rb} +0 -0
- data/spec/{dataset_methods_spec.rb → sequel_model/dataset_methods_spec.rb} +13 -1
- data/spec/{eager_loading_spec.rb → sequel_model/eager_loading_spec.rb} +75 -14
- data/spec/{hooks_spec.rb → sequel_model/hooks_spec.rb} +4 -4
- data/spec/sequel_model/inflector_spec.rb +119 -0
- data/spec/{model_spec.rb → sequel_model/model_spec.rb} +30 -11
- data/spec/{plugins_spec.rb → sequel_model/plugins_spec.rb} +0 -0
- data/spec/{record_spec.rb → sequel_model/record_spec.rb} +47 -6
- data/spec/{schema_spec.rb → sequel_model/schema_spec.rb} +18 -4
- data/spec/{spec_helper.rb → sequel_model/spec_helper.rb} +3 -2
- data/spec/{validations_spec.rb → sequel_model/validations_spec.rb} +37 -17
- data/spec/spec_config.rb +9 -0
- data/spec/spec_config.rb.example +10 -0
- metadata +110 -37
- data/spec/inflector_spec.rb +0 -34
@@ -0,0 +1,71 @@
|
|
1
|
+
module Sequel
|
2
|
+
module PrettyTable
|
3
|
+
# Prints nice-looking plain-text tables via puts
|
4
|
+
#
|
5
|
+
# +--+-------+
|
6
|
+
# |id|name |
|
7
|
+
# |--+-------|
|
8
|
+
# |1 |fasdfas|
|
9
|
+
# |2 |test |
|
10
|
+
# +--+-------+
|
11
|
+
def self.print(records, columns = nil) # records is an array of hashes
|
12
|
+
columns ||= records.first.keys.sort_by{|x|x.to_s}
|
13
|
+
sizes = column_sizes(records, columns)
|
14
|
+
sep_line = separator_line(columns, sizes)
|
15
|
+
|
16
|
+
puts sep_line
|
17
|
+
puts header_line(columns, sizes)
|
18
|
+
puts sep_line
|
19
|
+
records.each {|r| puts data_line(columns, sizes, r)}
|
20
|
+
puts sep_line
|
21
|
+
end
|
22
|
+
|
23
|
+
### Private Module Methods ###
|
24
|
+
|
25
|
+
# Hash of the maximum size of the value for each column
|
26
|
+
def self.column_sizes(records, columns) # :nodoc:
|
27
|
+
sizes = Hash.new {0}
|
28
|
+
columns.each do |c|
|
29
|
+
s = c.to_s.size
|
30
|
+
sizes[c.to_sym] = s if s > sizes[c.to_sym]
|
31
|
+
end
|
32
|
+
records.each do |r|
|
33
|
+
columns.each do |c|
|
34
|
+
s = r[c].to_s.size
|
35
|
+
sizes[c.to_sym] = s if s > sizes[c.to_sym]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
sizes
|
39
|
+
end
|
40
|
+
|
41
|
+
# String for each data line
|
42
|
+
def self.data_line(columns, sizes, record) # :nodoc:
|
43
|
+
'|' << columns.map {|c| format_cell(sizes[c], record[c])}.join('|') << '|'
|
44
|
+
end
|
45
|
+
|
46
|
+
# Format the value so it takes up exactly size characters
|
47
|
+
def self.format_cell(size, v) # :nodoc:
|
48
|
+
case v
|
49
|
+
when Bignum, Fixnum
|
50
|
+
"%#{size}d" % v
|
51
|
+
when Float
|
52
|
+
"%#{size}g" % v
|
53
|
+
else
|
54
|
+
"%-#{size}s" % v.to_s
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# String for header line
|
59
|
+
def self.header_line(columns, sizes) # :nodoc:
|
60
|
+
'|' << columns.map {|c| "%-#{sizes[c]}s" % c.to_s}.join('|') << '|'
|
61
|
+
end
|
62
|
+
|
63
|
+
# String for separtor line
|
64
|
+
def self.separator_line(columns, sizes) # :nodoc:
|
65
|
+
'+' << columns.map {|c| '-' * sizes[c]}.join('+') << '+'
|
66
|
+
end
|
67
|
+
|
68
|
+
private_class_method :column_sizes, :data_line, :format_cell, :header_line, :separator_line
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
@@ -0,0 +1,239 @@
|
|
1
|
+
module Sequel
|
2
|
+
# The Schema module holds the schema generators and the SQL code relating
|
3
|
+
# to SQL DDL (Data Definition Language).
|
4
|
+
module Schema
|
5
|
+
# Schema::Generator is used to create tables. It takes a Database
|
6
|
+
# object and a block of column/index/constraint specifications, and
|
7
|
+
# creates a table in the database based on the specifications.
|
8
|
+
#
|
9
|
+
# Schema::Generator has some methods but also includes method_missing,
|
10
|
+
# allowing users to specify column type as a method instead of using
|
11
|
+
# the column method, which makes for a nicer DSL.
|
12
|
+
#
|
13
|
+
# See Database#create_table.
|
14
|
+
class Generator
|
15
|
+
# Set the database in which to create the table, and evaluate the block
|
16
|
+
# in the context of this object.
|
17
|
+
def initialize(db, &block)
|
18
|
+
@db = db
|
19
|
+
@columns = []
|
20
|
+
@indexes = []
|
21
|
+
@primary_key = nil
|
22
|
+
instance_eval(&block) if block
|
23
|
+
end
|
24
|
+
|
25
|
+
# Add a unnamed constraint to the DDL, specified by the given block
|
26
|
+
# or args.
|
27
|
+
def check(*args, &block)
|
28
|
+
constraint(nil, *args, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Add a column with the given name, type, and opts to the DDL.
|
32
|
+
#
|
33
|
+
# You can also create columns via method missing, so the following are
|
34
|
+
# equivalent:
|
35
|
+
#
|
36
|
+
# column :number, :integer
|
37
|
+
# integer :number
|
38
|
+
#
|
39
|
+
# The following options are supported:
|
40
|
+
#
|
41
|
+
# * :default - The default value for the column.
|
42
|
+
# * :index - Create an index on this column.
|
43
|
+
# * :key - For foreign key columns, the column in the associated table
|
44
|
+
# that this column references. Unnecessary if this column
|
45
|
+
# references the primary key of the associated table.
|
46
|
+
# * :null - Mark the column as allowing NULL values (if true),
|
47
|
+
# or not allowing NULL values (if false). If unspecified, will default
|
48
|
+
# to whatever the database default is.
|
49
|
+
# * :on_delete - Specify the behavior of this column when being deleted.
|
50
|
+
# See Schema::SQL#on_delete_clause for options.
|
51
|
+
# * :size - The size of the column, generally used with string
|
52
|
+
# columns to specify the maximum number of characters the column will hold.
|
53
|
+
# * :unique - Mark the column is unique, generally has the same effect as
|
54
|
+
# creating a unique index on the column.
|
55
|
+
# * :unsigned - Make the column type unsigned, only useful for integer
|
56
|
+
# columns.
|
57
|
+
def column(name, type, opts = {})
|
58
|
+
@columns << {:name => name, :type => type}.merge(opts)
|
59
|
+
index(name) if opts[:index]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Adds a named constraint (or unnamed if name is nil) to the DDL,
|
63
|
+
# with the given block or args.
|
64
|
+
def constraint(name, *args, &block)
|
65
|
+
@columns << {:name => name, :type => :check, :check => block || args}
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return the DDL created by the generator as a array of two elements,
|
69
|
+
# the first being the columns and the second being the indexes.
|
70
|
+
def create_info
|
71
|
+
@columns.unshift(@primary_key) if @primary_key && !has_column?(primary_key_name)
|
72
|
+
[@columns, @indexes]
|
73
|
+
end
|
74
|
+
|
75
|
+
# Add a foreign key in the table that references another table to the DDL. See column
|
76
|
+
# for available options.
|
77
|
+
def foreign_key(name, table=nil, opts = {})
|
78
|
+
opts = case table
|
79
|
+
when Hash
|
80
|
+
table.merge(opts)
|
81
|
+
when Symbol
|
82
|
+
opts.merge(:table=>table)
|
83
|
+
when NilClass
|
84
|
+
opts
|
85
|
+
else
|
86
|
+
raise(Error, "The seconds argument to foreign_key should be a Hash, Symbol, or nil")
|
87
|
+
end
|
88
|
+
column(name, :integer, opts)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Add a full text index on the given columns to the DDL.
|
92
|
+
def full_text_index(columns, opts = {})
|
93
|
+
index(columns, opts.merge(:type => :full_text))
|
94
|
+
end
|
95
|
+
|
96
|
+
# True if the DDL includes the creation of a column with the given name.
|
97
|
+
def has_column?(name)
|
98
|
+
@columns.any?{|c| c[:name] == name}
|
99
|
+
end
|
100
|
+
|
101
|
+
# Add an index on the given column(s) with the given options to the DDL.
|
102
|
+
# The available options are:
|
103
|
+
#
|
104
|
+
# * :type - The type of index to use (only supported by some databases)
|
105
|
+
# * :unique - Make the index unique, so duplicate values are not allowed.
|
106
|
+
# * :where - Create a partial index (only supported by some databases)
|
107
|
+
def index(columns, opts = {})
|
108
|
+
@indexes << {:columns => Array(columns)}.merge(opts)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Add a column with the given type, name, and opts to the DDL. See column for available
|
112
|
+
# options.
|
113
|
+
def method_missing(type, name = nil, opts = {})
|
114
|
+
name ? column(name, type, opts) : super
|
115
|
+
end
|
116
|
+
|
117
|
+
# Add a column with the given name and primary key options to the DDL. You
|
118
|
+
# can optionally provide a type argument and/or an options hash argument
|
119
|
+
# to change the primary key options. See column for available options.
|
120
|
+
def primary_key(name, *args)
|
121
|
+
@primary_key = @db.serial_primary_key_options.merge({:name => name})
|
122
|
+
|
123
|
+
if opts = args.pop
|
124
|
+
opts = {:type => opts} unless opts.is_a?(Hash)
|
125
|
+
if type = args.pop
|
126
|
+
opts.merge!(:type => type)
|
127
|
+
end
|
128
|
+
@primary_key.merge!(opts)
|
129
|
+
end
|
130
|
+
@primary_key
|
131
|
+
end
|
132
|
+
|
133
|
+
# The name of the primary key for this table, if it has a primary key.
|
134
|
+
def primary_key_name
|
135
|
+
@primary_key[:name] if @primary_key
|
136
|
+
end
|
137
|
+
|
138
|
+
# Add a spatial index on the given columns to the DDL.
|
139
|
+
def spatial_index(columns, opts = {})
|
140
|
+
index(columns, opts.merge(:type => :spatial))
|
141
|
+
end
|
142
|
+
|
143
|
+
# Add a unique index on the given columns to the DDL.
|
144
|
+
def unique(columns, opts = {})
|
145
|
+
index(columns, opts.merge(:unique => true))
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# The Schema::AlterTableGenerator creates DDL operations on existing tables,
|
150
|
+
# such as adding/removing/modifying columns/indexes/constraints.
|
151
|
+
class AlterTableGenerator
|
152
|
+
# An array of DDL operations to perform
|
153
|
+
attr_reader :operations
|
154
|
+
|
155
|
+
# Set the Database object to which to apply the DDL, and evaluate the
|
156
|
+
# block in the context of this object.
|
157
|
+
def initialize(db, &block)
|
158
|
+
@db = db
|
159
|
+
@operations = []
|
160
|
+
instance_eval(&block) if block
|
161
|
+
end
|
162
|
+
|
163
|
+
# Add a column with the given name, type, and opts to the DDL for the table.
|
164
|
+
# See Generator#column for the available options.
|
165
|
+
def add_column(name, type, opts = {})
|
166
|
+
@operations << {:op => :add_column, :name => name, :type => type}.merge(opts)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Add a constraint with the given name and args to the DDL for the table.
|
170
|
+
# See Generator#constraint.
|
171
|
+
def add_constraint(name, *args, &block)
|
172
|
+
@operations << {:op => :add_constraint, :name => name, :type => :check, \
|
173
|
+
:check => block || args}
|
174
|
+
end
|
175
|
+
|
176
|
+
# Add a foreign key with the given name and referencing the given table
|
177
|
+
# to the DDL for the table. See Generator#column for the available options.
|
178
|
+
def add_foreign_key(name, table, opts = {})
|
179
|
+
add_column(name, :integer, {:table=>table}.merge(opts))
|
180
|
+
end
|
181
|
+
|
182
|
+
# Add a full text index on the given columns to the DDL for the table.
|
183
|
+
# See Generator#index for available options.
|
184
|
+
def add_full_text_index(columns, opts = {})
|
185
|
+
add_index(columns, {:type=>:full_text}.merge(opts))
|
186
|
+
end
|
187
|
+
|
188
|
+
# Add an index on the given columns to the DDL for the table. See
|
189
|
+
# Generator#index for available options.
|
190
|
+
def add_index(columns, opts = {})
|
191
|
+
@operations << {:op => :add_index, :columns => Array(columns)}.merge(opts)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Add a primary key to the DDL for the table. See Generator#column
|
195
|
+
# for the available options.
|
196
|
+
def add_primary_key(name, opts = {})
|
197
|
+
opts = @db.serial_primary_key_options.merge(opts)
|
198
|
+
add_column(name, opts.delete(:type), opts)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Add a spatial index on the given columns to the DDL for the table.
|
202
|
+
# See Generator#index for available options.
|
203
|
+
def add_spatial_index(columns, opts = {})
|
204
|
+
add_index(columns, {:type=>:spatial}.merge(opts))
|
205
|
+
end
|
206
|
+
|
207
|
+
# Remove a column from the DDL for the table.
|
208
|
+
def drop_column(name)
|
209
|
+
@operations << {:op => :drop_column, :name => name}
|
210
|
+
end
|
211
|
+
|
212
|
+
# Remove a constraint from the DDL for the table.
|
213
|
+
def drop_constraint(name)
|
214
|
+
@operations << {:op => :drop_constraint, :name => name}
|
215
|
+
end
|
216
|
+
|
217
|
+
# Remove an index from the DDL for the table.
|
218
|
+
def drop_index(columns)
|
219
|
+
@operations << {:op => :drop_index, :columns => Array(columns)}
|
220
|
+
end
|
221
|
+
|
222
|
+
# Modify a column's name in the DDL for the table.
|
223
|
+
def rename_column(name, new_name, opts = {})
|
224
|
+
@operations << {:op => :rename_column, :name => name, :new_name => new_name}.merge(opts)
|
225
|
+
end
|
226
|
+
|
227
|
+
# Modify a column's default value in the DDL for the table.
|
228
|
+
def set_column_default(name, default)
|
229
|
+
@operations << {:op => :set_column_default, :name => name, :default => default}
|
230
|
+
end
|
231
|
+
|
232
|
+
# Modify a column's type in the DDL for the table.
|
233
|
+
def set_column_type(name, type)
|
234
|
+
@operations << {:op => :set_column_type, :name => name, :type => type}
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
@@ -0,0 +1,325 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Schema
|
3
|
+
module SQL
|
4
|
+
AUTOINCREMENT = 'AUTOINCREMENT'.freeze
|
5
|
+
CASCADE = 'CASCADE'.freeze
|
6
|
+
COMMA_SEPARATOR = ', '.freeze
|
7
|
+
NO_ACTION = 'NO ACTION'.freeze
|
8
|
+
NOT_NULL = ' NOT NULL'.freeze
|
9
|
+
NULL = ' NULL'.freeze
|
10
|
+
PRIMARY_KEY = ' PRIMARY KEY'.freeze
|
11
|
+
RESTRICT = 'RESTRICT'.freeze
|
12
|
+
SET_DEFAULT = 'SET DEFAULT'.freeze
|
13
|
+
SET_NULL = 'SET NULL'.freeze
|
14
|
+
TYPES = Hash.new {|h, k| k}
|
15
|
+
TYPES[:double] = 'double precision'
|
16
|
+
UNDERSCORE = '_'.freeze
|
17
|
+
UNIQUE = ' UNIQUE'.freeze
|
18
|
+
UNSIGNED = ' UNSIGNED'.freeze
|
19
|
+
|
20
|
+
# The SQL to execute to modify the DDL for the given table name. op
|
21
|
+
# should be one of the operations returned by the AlterTableGenerator.
|
22
|
+
def alter_table_sql(table, op)
|
23
|
+
quoted_table = quote_identifier(table)
|
24
|
+
quoted_name = quote_identifier(op[:name]) if op[:name]
|
25
|
+
case op[:op]
|
26
|
+
when :add_column
|
27
|
+
"ALTER TABLE #{quoted_table} ADD COLUMN #{column_definition_sql(op)}"
|
28
|
+
when :drop_column
|
29
|
+
"ALTER TABLE #{quoted_table} DROP COLUMN #{quoted_name}"
|
30
|
+
when :rename_column
|
31
|
+
"ALTER TABLE #{quoted_table} RENAME COLUMN #{quoted_name} TO #{quote_identifier(op[:new_name])}"
|
32
|
+
when :set_column_type
|
33
|
+
"ALTER TABLE #{quoted_table} ALTER COLUMN #{quoted_name} TYPE #{op[:type]}"
|
34
|
+
when :set_column_default
|
35
|
+
"ALTER TABLE #{quoted_table} ALTER COLUMN #{quoted_name} SET DEFAULT #{literal(op[:default])}"
|
36
|
+
when :add_index
|
37
|
+
index_definition_sql(table, op)
|
38
|
+
when :drop_index
|
39
|
+
"DROP INDEX #{default_index_name(table, op[:columns])}"
|
40
|
+
when :add_constraint
|
41
|
+
"ALTER TABLE #{quoted_table} ADD #{constraint_definition_sql(op)}"
|
42
|
+
when :drop_constraint
|
43
|
+
"ALTER TABLE #{quoted_table} DROP CONSTRAINT #{quoted_name}"
|
44
|
+
else
|
45
|
+
raise Error, "Unsupported ALTER TABLE operation"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Array of SQL DDL modification statements for the given table,
|
50
|
+
# corresponding to the DDL changes specified by the operations.
|
51
|
+
def alter_table_sql_list(table, operations)
|
52
|
+
operations.map{|op| alter_table_sql(table, op)}
|
53
|
+
end
|
54
|
+
|
55
|
+
# The SQL string specify the autoincrement property, generally used by
|
56
|
+
# primary keys.
|
57
|
+
def auto_increment_sql
|
58
|
+
AUTOINCREMENT
|
59
|
+
end
|
60
|
+
|
61
|
+
# SQL DDL fragment containing the column creation SQL for the given column.
|
62
|
+
def column_definition_sql(column)
|
63
|
+
return constraint_definition_sql(column) if column[:type] == :check
|
64
|
+
sql = "#{quote_identifier(column[:name])} #{type_literal(TYPES[column[:type]])}"
|
65
|
+
column[:size] ||= 255 if column[:type] == :varchar
|
66
|
+
elements = column[:size] || column[:elements]
|
67
|
+
sql << literal(Array(elements)) if elements
|
68
|
+
sql << UNSIGNED if column[:unsigned]
|
69
|
+
sql << UNIQUE if column[:unique]
|
70
|
+
sql << NOT_NULL if column[:null] == false
|
71
|
+
sql << NULL if column[:null] == true
|
72
|
+
sql << " DEFAULT #{literal(column[:default])}" if column.include?(:default)
|
73
|
+
sql << PRIMARY_KEY if column[:primary_key]
|
74
|
+
sql << " #{auto_increment_sql}" if column[:auto_increment]
|
75
|
+
if column[:table]
|
76
|
+
sql << " REFERENCES #{quote_identifier(column[:table])}"
|
77
|
+
sql << "(#{quote_identifier(column[:key])})" if column[:key]
|
78
|
+
sql << " ON DELETE #{on_delete_clause(column[:on_delete])}" if column[:on_delete]
|
79
|
+
end
|
80
|
+
sql
|
81
|
+
end
|
82
|
+
|
83
|
+
# SQL DDL fragment containing the column creation
|
84
|
+
# SQL for all given columns, used instead a CREATE TABLE block.
|
85
|
+
def column_list_sql(columns)
|
86
|
+
columns.map{|c| column_definition_sql(c)}.join(COMMA_SEPARATOR)
|
87
|
+
end
|
88
|
+
|
89
|
+
# SQL DDL fragment specifying a constraint on a table.
|
90
|
+
def constraint_definition_sql(constraint)
|
91
|
+
sql = constraint[:name] ? "CONSTRAINT #{quote_identifier(constraint[:name])} " : ""
|
92
|
+
sql << "CHECK #{filter_expr(constraint[:check])}"
|
93
|
+
sql
|
94
|
+
end
|
95
|
+
|
96
|
+
# Array of SQL DDL statements, the first for creating a table with the given
|
97
|
+
# name and column specifications, and the others for specifying indexes on
|
98
|
+
# the table.
|
99
|
+
def create_table_sql_list(name, columns, indexes = nil)
|
100
|
+
sql = ["CREATE TABLE #{quote_identifier(name)} (#{column_list_sql(columns)})"]
|
101
|
+
sql.concat(index_list_sql_list(name, indexes)) if indexes && !indexes.empty?
|
102
|
+
sql
|
103
|
+
end
|
104
|
+
|
105
|
+
# Default index name for the table and columns, may be too long
|
106
|
+
# for certain databases.
|
107
|
+
def default_index_name(table_name, columns)
|
108
|
+
"#{table_name}_#{columns.join(UNDERSCORE)}_index"
|
109
|
+
end
|
110
|
+
|
111
|
+
# SQL DDL statement to drop the table with the given name.
|
112
|
+
def drop_table_sql(name)
|
113
|
+
"DROP TABLE #{quote_identifier(name)}"
|
114
|
+
end
|
115
|
+
|
116
|
+
# Proxy the filter_expr call to the dataset, used for creating constraints.
|
117
|
+
def filter_expr(*args, &block)
|
118
|
+
schema_utility_dataset.literal(schema_utility_dataset.send(:filter_expr, *args, &block))
|
119
|
+
end
|
120
|
+
|
121
|
+
# SQL DDL statement for creating an index for the table with the given name
|
122
|
+
# and index specifications.
|
123
|
+
def index_definition_sql(table_name, index)
|
124
|
+
index_name = index[:name] || default_index_name(table_name, index[:columns])
|
125
|
+
if index[:type]
|
126
|
+
raise Error, "Index types are not supported for this database"
|
127
|
+
elsif index[:where]
|
128
|
+
raise Error, "Partial indexes are not supported for this database"
|
129
|
+
else
|
130
|
+
"CREATE #{'UNIQUE ' if index[:unique]}INDEX #{index_name} ON #{quote_identifier(table_name)} #{literal(index[:columns])}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Array of SQL DDL statements, one for each index specification,
|
135
|
+
# for the given table.
|
136
|
+
def index_list_sql_list(table_name, indexes)
|
137
|
+
indexes.map{|i| index_definition_sql(table_name, i)}
|
138
|
+
end
|
139
|
+
|
140
|
+
# Proxy the literal call to the dataset, used for default values.
|
141
|
+
def literal(v)
|
142
|
+
schema_utility_dataset.literal(v)
|
143
|
+
end
|
144
|
+
|
145
|
+
# SQL DDL ON DELETE fragment to use, based on the given action.
|
146
|
+
# The following actions are recognized:
|
147
|
+
#
|
148
|
+
# * :cascade - Delete rows referencing this row.
|
149
|
+
# * :no_action (default) - Raise an error if other rows reference this
|
150
|
+
# row, allow deferring of the integrity check.
|
151
|
+
# * :restrict - Raise an error if other rows reference this row,
|
152
|
+
# but do not allow deferring the integrity check.
|
153
|
+
# * :set_default - Set columns referencing this row to their default value.
|
154
|
+
# * :set_null - Set columns referencing this row to NULL.
|
155
|
+
def on_delete_clause(action)
|
156
|
+
case action
|
157
|
+
when :restrict
|
158
|
+
RESTRICT
|
159
|
+
when :cascade
|
160
|
+
CASCADE
|
161
|
+
when :set_null
|
162
|
+
SET_NULL
|
163
|
+
when :set_default
|
164
|
+
SET_DEFAULT
|
165
|
+
else
|
166
|
+
NO_ACTION
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Proxy the quote_identifier method to the dataset, used for quoting tables and columns.
|
171
|
+
def quote_identifier(v)
|
172
|
+
schema_utility_dataset.quote_identifier(v)
|
173
|
+
end
|
174
|
+
|
175
|
+
# SQL DDL statement for renaming a table.
|
176
|
+
def rename_table_sql(name, new_name)
|
177
|
+
"ALTER TABLE #{quote_identifier(name)} RENAME TO #{quote_identifier(new_name)}"
|
178
|
+
end
|
179
|
+
|
180
|
+
# Parse the schema from the database using the SQL standard INFORMATION_SCHEMA.
|
181
|
+
# If the table_name is not given, returns the schema for all tables as a hash.
|
182
|
+
# If the table_name is given, returns the schema for a single table as an
|
183
|
+
# array with all members being arrays of length 2. Available options are:
|
184
|
+
#
|
185
|
+
# * :reload - Get fresh information from the database, instead of using
|
186
|
+
# cached information. If table_name is blank, :reload should be used
|
187
|
+
# unless you are sure that schema has not been called before with a
|
188
|
+
# table_name, otherwise you may only getting the schemas for tables
|
189
|
+
# that have been requested explicitly.
|
190
|
+
def schema(table_name = nil, opts={})
|
191
|
+
if opts[:reload] && @schemas
|
192
|
+
if table_name
|
193
|
+
@schemas.delete(table_name)
|
194
|
+
else
|
195
|
+
@schemas = nil
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
if table_name
|
200
|
+
return @schemas[table_name] if @schemas && @schemas[table_name]
|
201
|
+
else
|
202
|
+
return @schemas if @schemas
|
203
|
+
end
|
204
|
+
|
205
|
+
if table_name
|
206
|
+
@schemas ||= {}
|
207
|
+
@schemas[table_name] ||= schema_parse_table(table_name, opts)
|
208
|
+
else
|
209
|
+
@schemas = schema_parse_tables(opts)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# The dataset to use for proxying certain schema methods.
|
214
|
+
def schema_utility_dataset
|
215
|
+
@schema_utility_dataset ||= dataset
|
216
|
+
end
|
217
|
+
|
218
|
+
# SQL fragment specifying the type of a given column.
|
219
|
+
def type_literal(t)
|
220
|
+
t.is_a?(Symbol) ? t.to_s : literal(t)
|
221
|
+
end
|
222
|
+
|
223
|
+
private
|
224
|
+
|
225
|
+
# Match the database's column type to a ruby type via a
|
226
|
+
# regular expression. The following ruby types are supported:
|
227
|
+
# integer, string, date, datetime, boolean, and float.
|
228
|
+
def schema_column_type(db_type)
|
229
|
+
case db_type
|
230
|
+
when 'tinyint'
|
231
|
+
Sequel.convert_tinyint_to_bool ? :boolean : :integer
|
232
|
+
when /\A(int(eger)?|bigint|smallint)\z/
|
233
|
+
:integer
|
234
|
+
when /\A(character( varying)?|varchar|text)\z/
|
235
|
+
:string
|
236
|
+
when /\Adate\z/
|
237
|
+
:date
|
238
|
+
when /\A(datetime|timestamp( with(out)? time zone)?)\z/
|
239
|
+
:datetime
|
240
|
+
when /\Atime( with(out)? time zone)?\z/
|
241
|
+
:time
|
242
|
+
when "boolean"
|
243
|
+
:boolean
|
244
|
+
when /\A(real|float|double( precision)?)\z/
|
245
|
+
:float
|
246
|
+
when /\A(numeric|decimal|money)\z/
|
247
|
+
:decimal
|
248
|
+
when "bytea"
|
249
|
+
:blob
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# The final dataset used by the schema parser, after all
|
254
|
+
# options have been applied.
|
255
|
+
def schema_ds(table_name, opts)
|
256
|
+
schema_ds_dataset.from(*schema_ds_from(table_name, opts)) \
|
257
|
+
.select(*schema_ds_select(table_name, opts)) \
|
258
|
+
.join(*schema_ds_join(table_name, opts)) \
|
259
|
+
.filter(*schema_ds_filter(table_name, opts))
|
260
|
+
end
|
261
|
+
|
262
|
+
# The blank dataset used by the schema parser.
|
263
|
+
def schema_ds_dataset
|
264
|
+
schema_utility_dataset
|
265
|
+
end
|
266
|
+
|
267
|
+
# Argument array for the schema dataset's filter method.
|
268
|
+
def schema_ds_filter(table_name, opts)
|
269
|
+
if table_name
|
270
|
+
[{:c__table_name=>table_name.to_s}]
|
271
|
+
else
|
272
|
+
[{:t__table_type=>'BASE TABLE'}]
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Argument array for the schema dataset's from method.
|
277
|
+
def schema_ds_from(table_name, opts)
|
278
|
+
[:information_schema__tables___t]
|
279
|
+
end
|
280
|
+
|
281
|
+
# Argument array for the schema dataset's join method.
|
282
|
+
def schema_ds_join(table_name, opts)
|
283
|
+
[:information_schema__columns, [:table_catalog, :table_schema, :table_name], :c]
|
284
|
+
end
|
285
|
+
|
286
|
+
# Argument array for the schema dataset's select method.
|
287
|
+
def schema_ds_select(table_name, opts)
|
288
|
+
cols = [:column_name___column, :data_type___db_type, :character_maximum_length___max_chars, \
|
289
|
+
:numeric_precision, :column_default___default, :is_nullable___allow_null]
|
290
|
+
cols << :c__table_name unless table_name
|
291
|
+
cols
|
292
|
+
end
|
293
|
+
|
294
|
+
# Parse the schema for a given table.
|
295
|
+
def schema_parse_table(table_name, opts)
|
296
|
+
schema_parse_rows(schema_ds(table_name, opts))
|
297
|
+
end
|
298
|
+
|
299
|
+
# Parse the schema all tables in the database.
|
300
|
+
def schema_parse_tables(opts)
|
301
|
+
schemas = {}
|
302
|
+
schema_ds(nil, opts).each do |row|
|
303
|
+
(schemas[row.delete(:table_name).to_sym] ||= []) << row
|
304
|
+
end
|
305
|
+
schemas.each do |table, rows|
|
306
|
+
schemas[table] = schema_parse_rows(rows)
|
307
|
+
end
|
308
|
+
schemas
|
309
|
+
end
|
310
|
+
|
311
|
+
# Parse the output of the information schema columns into
|
312
|
+
# the hash used by Sequel.
|
313
|
+
def schema_parse_rows(rows)
|
314
|
+
schema = []
|
315
|
+
rows.each do |row|
|
316
|
+
row[:allow_null] = row[:allow_null] == 'YES' ? true : false
|
317
|
+
row[:default] = nil if row[:default].blank?
|
318
|
+
row[:type] = schema_column_type(row[:db_type])
|
319
|
+
schema << [row.delete(:column).to_sym, row]
|
320
|
+
end
|
321
|
+
schema
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|