sequel 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/CHANGELOG +1551 -4
  2. data/README +306 -19
  3. data/Rakefile +84 -56
  4. data/bin/sequel +106 -0
  5. data/doc/cheat_sheet.rdoc +225 -0
  6. data/doc/dataset_filtering.rdoc +182 -0
  7. data/lib/sequel_core.rb +136 -0
  8. data/lib/sequel_core/adapters/adapter_skeleton.rb +54 -0
  9. data/lib/sequel_core/adapters/ado.rb +80 -0
  10. data/lib/sequel_core/adapters/db2.rb +148 -0
  11. data/lib/sequel_core/adapters/dbi.rb +117 -0
  12. data/lib/sequel_core/adapters/informix.rb +78 -0
  13. data/lib/sequel_core/adapters/jdbc.rb +186 -0
  14. data/lib/sequel_core/adapters/jdbc/mysql.rb +55 -0
  15. data/lib/sequel_core/adapters/jdbc/postgresql.rb +66 -0
  16. data/lib/sequel_core/adapters/jdbc/sqlite.rb +47 -0
  17. data/lib/sequel_core/adapters/mysql.rb +231 -0
  18. data/lib/sequel_core/adapters/odbc.rb +155 -0
  19. data/lib/sequel_core/adapters/odbc_mssql.rb +106 -0
  20. data/lib/sequel_core/adapters/openbase.rb +64 -0
  21. data/lib/sequel_core/adapters/oracle.rb +170 -0
  22. data/lib/sequel_core/adapters/postgres.rb +199 -0
  23. data/lib/sequel_core/adapters/shared/mysql.rb +275 -0
  24. data/lib/sequel_core/adapters/shared/postgres.rb +351 -0
  25. data/lib/sequel_core/adapters/shared/sqlite.rb +146 -0
  26. data/lib/sequel_core/adapters/sqlite.rb +138 -0
  27. data/lib/sequel_core/connection_pool.rb +194 -0
  28. data/lib/sequel_core/core_ext.rb +203 -0
  29. data/lib/sequel_core/core_sql.rb +184 -0
  30. data/lib/sequel_core/database.rb +471 -0
  31. data/lib/sequel_core/database/schema.rb +156 -0
  32. data/lib/sequel_core/dataset.rb +457 -0
  33. data/lib/sequel_core/dataset/callback.rb +13 -0
  34. data/lib/sequel_core/dataset/convenience.rb +245 -0
  35. data/lib/sequel_core/dataset/pagination.rb +96 -0
  36. data/lib/sequel_core/dataset/query.rb +41 -0
  37. data/lib/sequel_core/dataset/schema.rb +15 -0
  38. data/lib/sequel_core/dataset/sql.rb +889 -0
  39. data/lib/sequel_core/deprecated.rb +26 -0
  40. data/lib/sequel_core/exceptions.rb +42 -0
  41. data/lib/sequel_core/migration.rb +187 -0
  42. data/lib/sequel_core/object_graph.rb +216 -0
  43. data/lib/sequel_core/pretty_table.rb +71 -0
  44. data/lib/sequel_core/schema.rb +2 -0
  45. data/lib/sequel_core/schema/generator.rb +239 -0
  46. data/lib/sequel_core/schema/sql.rb +325 -0
  47. data/lib/sequel_core/sql.rb +812 -0
  48. data/lib/sequel_model.rb +5 -1
  49. data/lib/sequel_model/association_reflection.rb +3 -8
  50. data/lib/sequel_model/base.rb +15 -10
  51. data/lib/sequel_model/inflector.rb +3 -5
  52. data/lib/sequel_model/plugins.rb +1 -1
  53. data/lib/sequel_model/record.rb +11 -3
  54. data/lib/sequel_model/schema.rb +4 -4
  55. data/lib/sequel_model/validations.rb +6 -1
  56. data/spec/adapters/ado_spec.rb +17 -0
  57. data/spec/adapters/informix_spec.rb +96 -0
  58. data/spec/adapters/mysql_spec.rb +764 -0
  59. data/spec/adapters/oracle_spec.rb +222 -0
  60. data/spec/adapters/postgres_spec.rb +441 -0
  61. data/spec/adapters/spec_helper.rb +7 -0
  62. data/spec/adapters/sqlite_spec.rb +400 -0
  63. data/spec/integration/dataset_test.rb +51 -0
  64. data/spec/integration/eager_loader_test.rb +702 -0
  65. data/spec/integration/schema_test.rb +102 -0
  66. data/spec/integration/spec_helper.rb +44 -0
  67. data/spec/integration/type_test.rb +43 -0
  68. data/spec/rcov.opts +2 -0
  69. data/spec/sequel_core/connection_pool_spec.rb +363 -0
  70. data/spec/sequel_core/core_ext_spec.rb +156 -0
  71. data/spec/sequel_core/core_sql_spec.rb +427 -0
  72. data/spec/sequel_core/database_spec.rb +964 -0
  73. data/spec/sequel_core/dataset_spec.rb +2977 -0
  74. data/spec/sequel_core/expression_filters_spec.rb +346 -0
  75. data/spec/sequel_core/migration_spec.rb +261 -0
  76. data/spec/sequel_core/object_graph_spec.rb +234 -0
  77. data/spec/sequel_core/pretty_table_spec.rb +58 -0
  78. data/spec/sequel_core/schema_generator_spec.rb +122 -0
  79. data/spec/sequel_core/schema_spec.rb +497 -0
  80. data/spec/sequel_core/spec_helper.rb +51 -0
  81. data/spec/{association_reflection_spec.rb → sequel_model/association_reflection_spec.rb} +6 -6
  82. data/spec/{associations_spec.rb → sequel_model/associations_spec.rb} +47 -18
  83. data/spec/{base_spec.rb → sequel_model/base_spec.rb} +2 -1
  84. data/spec/{caching_spec.rb → sequel_model/caching_spec.rb} +0 -0
  85. data/spec/{dataset_methods_spec.rb → sequel_model/dataset_methods_spec.rb} +13 -1
  86. data/spec/{eager_loading_spec.rb → sequel_model/eager_loading_spec.rb} +75 -14
  87. data/spec/{hooks_spec.rb → sequel_model/hooks_spec.rb} +4 -4
  88. data/spec/sequel_model/inflector_spec.rb +119 -0
  89. data/spec/{model_spec.rb → sequel_model/model_spec.rb} +30 -11
  90. data/spec/{plugins_spec.rb → sequel_model/plugins_spec.rb} +0 -0
  91. data/spec/{record_spec.rb → sequel_model/record_spec.rb} +47 -6
  92. data/spec/{schema_spec.rb → sequel_model/schema_spec.rb} +18 -4
  93. data/spec/{spec_helper.rb → sequel_model/spec_helper.rb} +3 -2
  94. data/spec/{validations_spec.rb → sequel_model/validations_spec.rb} +37 -17
  95. data/spec/spec_config.rb +9 -0
  96. data/spec/spec_config.rb.example +10 -0
  97. metadata +110 -37
  98. 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,2 @@
1
+ require 'sequel_core/schema/generator'
2
+ require 'sequel_core/schema/sql'
@@ -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