sequel_core 1.5.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/CHANGELOG +116 -0
  2. data/COPYING +19 -19
  3. data/README +83 -32
  4. data/Rakefile +9 -20
  5. data/bin/sequel +43 -112
  6. data/doc/cheat_sheet.rdoc +225 -0
  7. data/doc/dataset_filtering.rdoc +257 -0
  8. data/lib/sequel_core/adapters/adapter_skeleton.rb +4 -2
  9. data/lib/sequel_core/adapters/ado.rb +3 -1
  10. data/lib/sequel_core/adapters/db2.rb +4 -2
  11. data/lib/sequel_core/adapters/dbi.rb +127 -113
  12. data/lib/sequel_core/adapters/informix.rb +4 -2
  13. data/lib/sequel_core/adapters/jdbc.rb +5 -3
  14. data/lib/sequel_core/adapters/mysql.rb +112 -46
  15. data/lib/sequel_core/adapters/odbc.rb +5 -7
  16. data/lib/sequel_core/adapters/odbc_mssql.rb +12 -3
  17. data/lib/sequel_core/adapters/openbase.rb +3 -1
  18. data/lib/sequel_core/adapters/oracle.rb +11 -9
  19. data/lib/sequel_core/adapters/postgres.rb +261 -262
  20. data/lib/sequel_core/adapters/sqlite.rb +72 -22
  21. data/lib/sequel_core/connection_pool.rb +140 -73
  22. data/lib/sequel_core/core_ext.rb +201 -66
  23. data/lib/sequel_core/core_sql.rb +123 -153
  24. data/lib/sequel_core/database/schema.rb +156 -0
  25. data/lib/sequel_core/database.rb +321 -338
  26. data/lib/sequel_core/dataset/callback.rb +11 -12
  27. data/lib/sequel_core/dataset/convenience.rb +213 -240
  28. data/lib/sequel_core/dataset/pagination.rb +58 -43
  29. data/lib/sequel_core/dataset/parse_tree_sequelizer.rb +331 -0
  30. data/lib/sequel_core/dataset/query.rb +41 -0
  31. data/lib/sequel_core/dataset/schema.rb +15 -0
  32. data/lib/sequel_core/dataset/sequelizer.rb +41 -373
  33. data/lib/sequel_core/dataset/sql.rb +741 -632
  34. data/lib/sequel_core/dataset.rb +183 -168
  35. data/lib/sequel_core/deprecated.rb +1 -169
  36. data/lib/sequel_core/exceptions.rb +24 -19
  37. data/lib/sequel_core/migration.rb +44 -52
  38. data/lib/sequel_core/object_graph.rb +43 -42
  39. data/lib/sequel_core/pretty_table.rb +71 -76
  40. data/lib/sequel_core/schema/generator.rb +163 -105
  41. data/lib/sequel_core/schema/sql.rb +250 -93
  42. data/lib/sequel_core/schema.rb +2 -8
  43. data/lib/sequel_core/sql.rb +394 -0
  44. data/lib/sequel_core/worker.rb +37 -27
  45. data/lib/sequel_core.rb +99 -45
  46. data/spec/adapters/informix_spec.rb +0 -1
  47. data/spec/adapters/mysql_spec.rb +177 -124
  48. data/spec/adapters/oracle_spec.rb +0 -1
  49. data/spec/adapters/postgres_spec.rb +98 -58
  50. data/spec/adapters/sqlite_spec.rb +45 -4
  51. data/spec/blockless_filters_spec.rb +269 -0
  52. data/spec/connection_pool_spec.rb +21 -18
  53. data/spec/core_ext_spec.rb +169 -19
  54. data/spec/core_sql_spec.rb +56 -49
  55. data/spec/database_spec.rb +78 -17
  56. data/spec/dataset_spec.rb +300 -428
  57. data/spec/migration_spec.rb +1 -1
  58. data/spec/object_graph_spec.rb +5 -11
  59. data/spec/rcov.opts +1 -1
  60. data/spec/schema_generator_spec.rb +16 -4
  61. data/spec/schema_spec.rb +89 -10
  62. data/spec/sequelizer_spec.rb +56 -56
  63. data/spec/spec.opts +0 -5
  64. data/spec/spec_config.rb +7 -0
  65. data/spec/spec_config.rb.example +5 -5
  66. data/spec/spec_helper.rb +6 -0
  67. data/spec/worker_spec.rb +1 -1
  68. metadata +78 -63
@@ -1,67 +1,70 @@
1
1
  module Sequel
2
2
  module Schema
3
3
  module SQL
4
- RESTRICT = 'RESTRICT'.freeze
5
- CASCADE = 'CASCADE'.freeze
6
- NO_ACTION = 'NO ACTION'.freeze
7
- SET_NULL = 'SET NULL'.freeze
8
- SET_DEFAULT = 'SET DEFAULT'.freeze
9
-
10
- def on_delete_clause(action)
11
- case action
12
- when :restrict
13
- RESTRICT
14
- when :cascade
15
- CASCADE
16
- when :set_null
17
- SET_NULL
18
- when :set_default
19
- SET_DEFAULT
20
- else
21
- NO_ACTION
22
- end
23
- end
24
-
25
4
  AUTOINCREMENT = 'AUTOINCREMENT'.freeze
26
-
27
- def auto_increment_sql
28
- AUTOINCREMENT
29
- end
30
-
5
+ CASCADE = 'CASCADE'.freeze
31
6
  COMMA_SEPARATOR = ', '.freeze
32
- UNIQUE = ' UNIQUE'.freeze
7
+ NO_ACTION = 'NO ACTION'.freeze
33
8
  NOT_NULL = ' NOT NULL'.freeze
34
9
  NULL = ' NULL'.freeze
35
- UNSIGNED = ' UNSIGNED'.freeze
36
10
  PRIMARY_KEY = ' PRIMARY KEY'.freeze
37
-
11
+ RESTRICT = 'RESTRICT'.freeze
12
+ SET_DEFAULT = 'SET DEFAULT'.freeze
13
+ SET_NULL = 'SET NULL'.freeze
38
14
  TYPES = Hash.new {|h, k| k}
39
15
  TYPES[:double] = 'double precision'
40
-
41
- def schema_utility_dataset
42
- @schema_utility_dataset ||= dataset
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
43
47
  end
44
-
45
- def literal(v)
46
- schema_utility_dataset.literal(v)
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)}
47
53
  end
48
54
 
49
- def type_literal(t)
50
- t.is_a?(Symbol) ? t.to_s : literal(t)
55
+ # The SQL string specify the autoincrement property, generally used by
56
+ # primary keys.
57
+ def auto_increment_sql
58
+ AUTOINCREMENT
51
59
  end
52
60
 
53
- def expression_list(*args, &block)
54
- schema_utility_dataset.send(:expression_list, *args, &block)
55
- end
56
-
61
+ # SQL DDL fragment containing the column creation SQL for the given column.
57
62
  def column_definition_sql(column)
58
- if column[:type] == :check
59
- return constraint_definition_sql(column)
60
- end
61
- sql = "#{literal(column[:name].to_sym)} #{type_literal(TYPES[column[:type]])}"
63
+ return constraint_definition_sql(column) if column[:type] == :check
64
+ sql = "#{quote_identifier(column[:name])} #{type_literal(TYPES[column[:type]])}"
62
65
  column[:size] ||= 255 if column[:type] == :varchar
63
66
  elements = column[:size] || column[:elements]
64
- sql << "(#{literal(elements)})" if elements
67
+ sql << literal(Array(elements)) if elements
65
68
  sql << UNSIGNED if column[:unsigned]
66
69
  sql << UNIQUE if column[:unique]
67
70
  sql << NOT_NULL if column[:null] == false
@@ -70,92 +73,246 @@ module Sequel
70
73
  sql << PRIMARY_KEY if column[:primary_key]
71
74
  sql << " #{auto_increment_sql}" if column[:auto_increment]
72
75
  if column[:table]
73
- sql << " REFERENCES #{column[:table]}"
74
- sql << "(#{column[:key]})" if column[:key]
76
+ sql << " REFERENCES #{quote_identifier(column[:table])}"
77
+ sql << "(#{quote_identifier(column[:key])})" if column[:key]
75
78
  sql << " ON DELETE #{on_delete_clause(column[:on_delete])}" if column[:on_delete]
76
79
  end
77
80
  sql
78
81
  end
79
82
 
80
- def constraint_definition_sql(column)
81
- sql = column[:name] ? "CONSTRAINT #{literal(column[:name].to_sym)} " : ""
82
-
83
- sql << "CHECK #{expression_list(column[:check], true)}"
84
- sql
85
- end
86
-
83
+ # SQL DDL fragment containing the column creation
84
+ # SQL for all given columns, used instead a CREATE TABLE block.
87
85
  def column_list_sql(columns)
88
- columns.map {|c| column_definition_sql(c)}.join(COMMA_SEPARATOR)
86
+ columns.map{|c| column_definition_sql(c)}.join(COMMA_SEPARATOR)
89
87
  end
90
88
 
91
- UNDERSCORE = '_'.freeze
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
92
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.
93
107
  def default_index_name(table_name, columns)
94
108
  "#{table_name}_#{columns.join(UNDERSCORE)}_index"
95
109
  end
96
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.
97
123
  def index_definition_sql(table_name, index)
98
124
  index_name = index[:name] || default_index_name(table_name, index[:columns])
99
125
  if index[:type]
100
126
  raise Error, "Index types are not supported for this database"
101
127
  elsif index[:where]
102
128
  raise Error, "Partial indexes are not supported for this database"
103
- elsif index[:unique]
104
- "CREATE UNIQUE INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
105
129
  else
106
- "CREATE INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
130
+ "CREATE #{'UNIQUE ' if index[:unique]}INDEX #{index_name} ON #{quote_identifier(table_name)} #{literal(index[:columns])}"
107
131
  end
108
132
  end
109
133
 
134
+ # Array of SQL DDL statements, one for each index specification,
135
+ # for the given table.
110
136
  def index_list_sql_list(table_name, indexes)
111
- indexes.map {|i| index_definition_sql(table_name, i)}
137
+ indexes.map{|i| index_definition_sql(table_name, i)}
112
138
  end
113
139
 
114
- def create_table_sql_list(name, columns, indexes = nil)
115
- sql = ["CREATE TABLE #{name} (#{column_list_sql(columns)})"]
116
- if indexes && !indexes.empty?
117
- sql.concat(index_list_sql_list(name, indexes))
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
118
167
  end
119
- sql
120
168
  end
121
169
 
122
- def drop_table_sql(name)
123
- "DROP TABLE #{name}"
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)
124
173
  end
125
174
 
175
+ # SQL DDL statement for renaming a table.
126
176
  def rename_table_sql(name, new_name)
127
- "ALTER TABLE #{name} RENAME TO #{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
128
211
  end
129
212
 
130
- def alter_table_sql_list(table, operations)
131
- operations.map {|op| alter_table_sql(table, op)}
213
+ # The dataset to use for proxying certain schema methods.
214
+ def schema_utility_dataset
215
+ @schema_utility_dataset ||= dataset
132
216
  end
133
217
 
134
- def alter_table_sql(table, op)
135
- case op[:op]
136
- when :add_column
137
- "ALTER TABLE #{table} ADD COLUMN #{column_definition_sql(op)}"
138
- when :drop_column
139
- "ALTER TABLE #{table} DROP COLUMN #{literal(op[:name])}"
140
- when :rename_column
141
- "ALTER TABLE #{table} RENAME COLUMN #{literal(op[:name])} TO #{literal(op[:new_name])}"
142
- when :set_column_type
143
- "ALTER TABLE #{table} ALTER COLUMN #{literal(op[:name])} TYPE #{op[:type]}"
144
- when :set_column_default
145
- "ALTER TABLE #{table} ALTER COLUMN #{literal(op[:name])} SET DEFAULT #{literal(op[:default])}"
146
- when :add_index
147
- index_definition_sql(table, op)
148
- when :drop_index
149
- "DROP INDEX #{default_index_name(table, op[:columns])}"
150
- when :add_constraint
151
- "ALTER TABLE #{table} ADD #{constraint_definition_sql(op)}"
152
- when :drop_constraint
153
- "ALTER TABLE #{table} DROP CONSTRAINT #{literal(op[:name])}"
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 /\A(int(eger)?|bigint|smallint)\z/
231
+ :integer
232
+ when /\A(character( varying)?|varchar|text)\z/
233
+ :string
234
+ when /\A(date)\z/
235
+ :date
236
+ when /\A(datetime|time|timestamp( with(out)? time zone)?)\z/
237
+ :datetime
238
+ when /\A(boolean|tinyint)\z/
239
+ :boolean
240
+ when /\A(real|float|double( precision)?)\z/
241
+ :float
242
+ end
243
+ end
244
+
245
+ # The final dataset used by the schema parser, after all
246
+ # options have been applied.
247
+ def schema_ds(table_name, opts)
248
+ schema_ds_dataset.from(*schema_ds_from(table_name, opts)) \
249
+ .select(*schema_ds_select(table_name, opts)) \
250
+ .join(*schema_ds_join(table_name, opts)) \
251
+ .filter(*schema_ds_filter(table_name, opts))
252
+ end
253
+
254
+ # The blank dataset used by the schema parser.
255
+ def schema_ds_dataset
256
+ schema_utility_dataset
257
+ end
258
+
259
+ # Argument array for the schema dataset's filter method.
260
+ def schema_ds_filter(table_name, opts)
261
+ if table_name
262
+ [{:c__table_name=>table_name.to_s}]
154
263
  else
155
- raise Error, "Unsupported ALTER TABLE operation"
264
+ [{:t__table_type=>'BASE TABLE'}]
156
265
  end
157
266
  end
267
+
268
+ # Argument array for the schema dataset's from method.
269
+ def schema_ds_from(table_name, opts)
270
+ [:information_schema__tables___t]
271
+ end
272
+
273
+ # Argument array for the schema dataset's join method.
274
+ def schema_ds_join(table_name, opts)
275
+ [:information_schema__columns, {:table_catalog=>:table_catalog,
276
+ :table_schema => :table_schema, :table_name => :table_name} , :c]
277
+ end
278
+
279
+ # Argument array for the schema dataset's select method.
280
+ def schema_ds_select(table_name, opts)
281
+ cols = [:column_name___column, :data_type___db_type, :character_maximum_length___max_chars, \
282
+ :numeric_precision, :column_default___default, :is_nullable___allow_null]
283
+ cols << :c__table_name unless table_name
284
+ cols
285
+ end
286
+
287
+ # Parse the schema for a given table.
288
+ def schema_parse_table(table_name, opts)
289
+ schema_parse_rows(schema_ds(table_name, opts))
290
+ end
291
+
292
+ # Parse the schema all tables in the database.
293
+ def schema_parse_tables(opts)
294
+ schemas = {}
295
+ schema_ds(nil, opts).each do |row|
296
+ (schemas[row.delete(:table_name).to_sym] ||= []) << row
297
+ end
298
+ schemas.each do |table, rows|
299
+ schemas[table] = schema_parse_rows(rows)
300
+ end
301
+ schemas
302
+ end
303
+
304
+ # Parse the output of the information schema columns into
305
+ # the hash used by Sequel.
306
+ def schema_parse_rows(rows)
307
+ schema = []
308
+ rows.each do |row|
309
+ row[:allow_null] = row[:allow_null] == 'YES' ? true : false
310
+ row[:default] = nil if row[:default].blank?
311
+ row[:type] = schema_column_type(row[:db_type])
312
+ schema << [row.delete(:column).to_sym, row]
313
+ end
314
+ schema
315
+ end
158
316
  end
159
317
  end
160
318
  end
161
-
@@ -1,8 +1,2 @@
1
- module Sequel
2
- module Schema
3
- end
4
- end
5
-
6
- require File.join(File.dirname(__FILE__), 'schema/sql')
7
- require File.join(File.dirname(__FILE__), 'schema/generator')
8
-
1
+ require 'sequel_core/schema/generator'
2
+ require 'sequel_core/schema/sql'