sequel 3.0.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. data/CHANGELOG +100 -0
  2. data/README.rdoc +3 -3
  3. data/bin/sequel +102 -19
  4. data/doc/reflection.rdoc +83 -0
  5. data/doc/release_notes/3.1.0.txt +406 -0
  6. data/lib/sequel/adapters/ado.rb +11 -0
  7. data/lib/sequel/adapters/amalgalite.rb +5 -20
  8. data/lib/sequel/adapters/do.rb +44 -36
  9. data/lib/sequel/adapters/firebird.rb +29 -43
  10. data/lib/sequel/adapters/jdbc.rb +17 -27
  11. data/lib/sequel/adapters/mysql.rb +35 -40
  12. data/lib/sequel/adapters/odbc.rb +4 -23
  13. data/lib/sequel/adapters/oracle.rb +22 -19
  14. data/lib/sequel/adapters/postgres.rb +6 -15
  15. data/lib/sequel/adapters/shared/mssql.rb +1 -1
  16. data/lib/sequel/adapters/shared/mysql.rb +29 -10
  17. data/lib/sequel/adapters/shared/oracle.rb +6 -8
  18. data/lib/sequel/adapters/shared/postgres.rb +28 -72
  19. data/lib/sequel/adapters/shared/sqlite.rb +5 -3
  20. data/lib/sequel/adapters/sqlite.rb +5 -20
  21. data/lib/sequel/adapters/utils/savepoint_transactions.rb +80 -0
  22. data/lib/sequel/adapters/utils/unsupported.rb +0 -12
  23. data/lib/sequel/core.rb +12 -3
  24. data/lib/sequel/core_sql.rb +1 -8
  25. data/lib/sequel/database.rb +107 -43
  26. data/lib/sequel/database/schema_generator.rb +1 -0
  27. data/lib/sequel/database/schema_methods.rb +38 -4
  28. data/lib/sequel/dataset.rb +6 -0
  29. data/lib/sequel/dataset/convenience.rb +2 -2
  30. data/lib/sequel/dataset/graph.rb +2 -2
  31. data/lib/sequel/dataset/prepared_statements.rb +3 -8
  32. data/lib/sequel/dataset/sql.rb +93 -19
  33. data/lib/sequel/extensions/blank.rb +2 -1
  34. data/lib/sequel/extensions/inflector.rb +4 -3
  35. data/lib/sequel/extensions/migration.rb +13 -2
  36. data/lib/sequel/extensions/pagination.rb +4 -0
  37. data/lib/sequel/extensions/pretty_table.rb +4 -0
  38. data/lib/sequel/extensions/query.rb +4 -0
  39. data/lib/sequel/extensions/schema_dumper.rb +100 -24
  40. data/lib/sequel/extensions/string_date_time.rb +3 -4
  41. data/lib/sequel/model.rb +2 -1
  42. data/lib/sequel/model/associations.rb +96 -38
  43. data/lib/sequel/model/base.rb +14 -14
  44. data/lib/sequel/model/plugins.rb +32 -21
  45. data/lib/sequel/plugins/caching.rb +13 -15
  46. data/lib/sequel/plugins/identity_map.rb +107 -0
  47. data/lib/sequel/plugins/lazy_attributes.rb +65 -0
  48. data/lib/sequel/plugins/many_through_many.rb +188 -0
  49. data/lib/sequel/plugins/schema.rb +13 -0
  50. data/lib/sequel/plugins/serialization.rb +53 -37
  51. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  52. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  53. data/lib/sequel/plugins/validation_class_methods.rb +28 -7
  54. data/lib/sequel/plugins/validation_helpers.rb +31 -24
  55. data/lib/sequel/sql.rb +16 -0
  56. data/lib/sequel/version.rb +1 -1
  57. data/spec/adapters/ado_spec.rb +47 -1
  58. data/spec/adapters/firebird_spec.rb +39 -36
  59. data/spec/adapters/mysql_spec.rb +25 -9
  60. data/spec/adapters/postgres_spec.rb +11 -24
  61. data/spec/core/database_spec.rb +54 -13
  62. data/spec/core/dataset_spec.rb +147 -29
  63. data/spec/core/object_graph_spec.rb +6 -1
  64. data/spec/core/schema_spec.rb +34 -0
  65. data/spec/core/spec_helper.rb +0 -2
  66. data/spec/extensions/caching_spec.rb +7 -0
  67. data/spec/extensions/identity_map_spec.rb +158 -0
  68. data/spec/extensions/lazy_attributes_spec.rb +113 -0
  69. data/spec/extensions/many_through_many_spec.rb +813 -0
  70. data/spec/extensions/migration_spec.rb +4 -4
  71. data/spec/extensions/schema_dumper_spec.rb +114 -13
  72. data/spec/extensions/schema_spec.rb +19 -3
  73. data/spec/extensions/serialization_spec.rb +28 -0
  74. data/spec/extensions/single_table_inheritance_spec.rb +25 -1
  75. data/spec/extensions/spec_helper.rb +2 -7
  76. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  77. data/spec/extensions/validation_class_methods_spec.rb +10 -5
  78. data/spec/integration/dataset_test.rb +39 -6
  79. data/spec/integration/eager_loader_test.rb +7 -7
  80. data/spec/integration/spec_helper.rb +0 -1
  81. data/spec/integration/transaction_test.rb +28 -1
  82. data/spec/model/association_reflection_spec.rb +29 -3
  83. data/spec/model/associations_spec.rb +1 -0
  84. data/spec/model/eager_loading_spec.rb +70 -1
  85. data/spec/model/plugins_spec.rb +236 -50
  86. data/spec/model/spec_helper.rb +0 -2
  87. metadata +18 -5
data/CHANGELOG CHANGED
@@ -1,3 +1,103 @@
1
+ === 3.1.0 (2009-06-04)
2
+
3
+ * Require the classes match to consider an association a reciprocal (jeremyevans) (#270)
4
+
5
+ * Make Migrator work correctly with file names like 001_873465873465873465_some_name.rb (jeremyevans) (#267)
6
+
7
+ * Add Dataset#qualify_to and #qualify_to_first_source, for qualifying unqualified identifiers in the dataset (jeremyevans)
8
+
9
+ * All the use of #sql_subscript on most SQL::* objects, and support non-integer subscript values (jeremyevans)
10
+
11
+ * Add reflection.rdoc file which explains and gives examples of many of Sequel's reflection methods (jeremyevans)
12
+
13
+ * Add many_through_many plugin, allowing you to construct an association to multiple objects through multiple join tables (jeremyevans)
14
+
15
+ * Add the :cartesian_product_number option to associations, for specifying if they can cause a cartesian product (jeremyevans)
16
+
17
+ * Make :eager_graph association option work correctly when lazily loading many_to_many associations (jeremyevans)
18
+
19
+ * Make eager_unique_table_alias consider joined tables as well as tables in the FROM clause (jeremyevans)
20
+
21
+ * Make add_graph_aliases work correctly even if set_graph_aliases hasn't been used (jeremyevans)
22
+
23
+ * Fix using :conditions that are a placeholder string in an association (e.g. :conditions=>['a = ?', 42]) (jeremyevans)
24
+
25
+ * On MySQL, make Dataset#insert_ignore affect #insert as well as #multi_insert and #import (jeremyevans, tmm1)
26
+
27
+ * Add -t option to bin/sequel to output the full backtrace if an exception is raised (jeremyevans)
28
+
29
+ * Make schema_dumper extension ignore errors with indexes unless it is dumping in the database-specific type format (jeremyevans)
30
+
31
+ * Don't dump partial indexes in the MySQL adapter (jeremyevans)
32
+
33
+ * Add :ignore_index_errors option to Database#create_table and :ignore_errors option to Database#add_index (jeremyevans)
34
+
35
+ * Make graphing a complex dataset work correctly (jeremyevans)
36
+
37
+ * Fix MySQL command out of sync errors, disconnect from database if they occur (jeremyevans)
38
+
39
+ * In the schema_dumper extension, do a much better job of parsing defaults from the database (jeremyevans)
40
+
41
+ * On PostgreSQL, assume the public schema if one is not given and there is no default in Database#tables (jeremyevans)
42
+
43
+ * Ignore a :default value if creating a String :text=>true or File column on MySQL, since it doesn't support defaults on text/blob columns (jeremyevans)
44
+
45
+ * On PostgreSQL, do not raise an error when attempting to reset the primary key sequence for a table without a primary key (jeremyevans)
46
+
47
+ * Allow plugins to have a configure method that is called on every attempt to load them (jeremyevans)
48
+
49
+ * Attempting to load an already loaded plugin no longer calls the plugin's apply method (jeremyevans)
50
+
51
+ * Make plugin's plugin_opts methods return an array of arguments if multiple arguments were given, instead of just the first argument (jeremyevans)
52
+
53
+ * Keep track of loaded plugins at Model.plugins, allows plugins to depend on other plugins (jeremyevans)
54
+
55
+ * Make Dataset#insert on PostgreSQL work with static SQL (jeremyevans)
56
+
57
+ * Add lazy_attributes plugin, for creating attributes that can be lazily loaded from the database (jeremyevans)
58
+
59
+ * Add tactical_eager_loading plugin, similar to DataMapper's strategic eager loading (jeremyevans)
60
+
61
+ * Don't raise an error when loading a plugin with DatasetMethods where none of the methods are public (jeremyevans)
62
+
63
+ * Add identity_map plugin, for creating temporary thread-local identity maps with some caching (jeremyevans)
64
+
65
+ * Support savepoints when using MySQL and SQLite (jeremyevans)
66
+
67
+ * Add -C option to bin/sequel that copies one database to another (jeremyevans)
68
+
69
+ * In the schema_dumper extension, don't include defaults that contain literal strings unless the DBs are the same (jeremyevans)
70
+
71
+ * Only include valid non-partial indexes of simple column references in the PostgreSQL adapter (jeremyevans)
72
+
73
+ * Add -h option to bin/sequel for outputting the usage, alias for -? (jeremyevans)
74
+
75
+ * Add -d and -D options to bin/sequel for dumping schema migrations (jeremyevans)
76
+
77
+ * Support eager graphing for model tables that lack primary keys (jeremyevans)
78
+
79
+ * Add Model.create_table? to the schema plugin, similar to Database#create_table? (jeremyevans)
80
+
81
+ * Add Database#create_table?, which creates the table if it doesn't already exist (jeremyevans)
82
+
83
+ * Handle ordered and limited datasets correctly when using UNION, INTERSECT, or EXCEPT (jeremyevans)
84
+
85
+ * Fix unlikely threading bug with class level validations (jeremyevans)
86
+
87
+ * Make the schema_dumper extension dump tables in alphabetical order in migrations (jeremyevans)
88
+
89
+ * Add Sequel.extension method for loading extensions, so you don't have to use require (jeremyevans)
90
+
91
+ * Allow bin/sequel to respect multiple -L options instead of ignoring all but the last one (jeremyevans)
92
+
93
+ * Add :command_timeout and :provider options to ADO adapter (hgimenez)
94
+
95
+ * Fix exception messages when Sequel.string_to_* fail (jeremyevans)
96
+
97
+ * Fix String :type=>:text generic type in the Firebird adapter (wishdev)
98
+
99
+ * Add Sequel.amalgalite adapter method (jeremyevans)
100
+
1
101
  === 3.0.0 (2009-05-04)
2
102
 
3
103
  * Remove dead threads from connection pool if the pool is full and a connection is requested (jeremyevans)
data/README.rdoc CHANGED
@@ -44,15 +44,15 @@ If you have any comments or suggestions please post to the Google group.
44
44
 
45
45
  DB.create_table :items do
46
46
  primary_key :id
47
- String name
48
- Float price
47
+ String :name
48
+ Float :price
49
49
  end
50
50
 
51
51
  items = DB[:items] # Create a dataset
52
52
 
53
53
  # Populate the table
54
54
  items.insert(:name => 'abc', :price => rand * 100)
55
- items.insert(name => 'def', :price => rand * 100)
55
+ items.insert(:name => 'def', :price => rand * 100)
56
56
  items.insert(:name => 'ghi', :price => rand * 100)
57
57
 
58
58
  # Print out the number of records
data/bin/sequel CHANGED
@@ -5,12 +5,15 @@ require 'optparse'
5
5
  require 'sequel'
6
6
 
7
7
  db_opts = {}
8
+ copy_databases = nil
9
+ dump_migration = nil
8
10
  echo = nil
9
11
  env = nil
10
12
  logfile = nil
11
13
  migrate_dir = nil
12
14
  migrate_ver = nil
13
- load_dir = nil
15
+ backtrace = nil
16
+ load_dirs = []
14
17
 
15
18
  opts = OptionParser.new do |opts|
16
19
  opts.banner = "Sequel: The Database Toolkit for Ruby"
@@ -25,11 +28,23 @@ opts = OptionParser.new do |opts|
25
28
  opts.separator ""
26
29
  opts.separator "Options:"
27
30
 
28
- opts.on_tail("-?", "--help", "Show this message") do
31
+ opts.on_tail("-h", "-?", "--help", "Show this message") do
29
32
  puts opts
30
33
  exit
31
34
  end
32
35
 
36
+ opts.on("-C", "--copy-databases", "copy one database to another") do
37
+ copy_databases = true
38
+ end
39
+
40
+ opts.on("-d", "--dump-migration", "print database migration to STDOUT") do
41
+ dump_migration = true
42
+ end
43
+
44
+ opts.on("-D", "--dump-migration-same-db", "print database migration to STDOUT without type translation") do
45
+ dump_migration = :same_db
46
+ end
47
+
33
48
  opts.on("-e", "--env ENV", "use environment config for database") do |v|
34
49
  env = v
35
50
  end
@@ -42,8 +57,8 @@ opts = OptionParser.new do |opts|
42
57
  logfile = v
43
58
  end
44
59
 
45
- opts.on("-L", "--load-dir DIR", "loads all *.rb from specifed directory") do |v|
46
- load_dir = v
60
+ opts.on("-L", "--load-dir DIR", "loads all *.rb under specifed directory") do |v|
61
+ load_dirs << v
47
62
  end
48
63
 
49
64
  opts.on("-m", "--migrate-directory DIR", "run the migrations in directory") do |v|
@@ -54,6 +69,10 @@ opts = OptionParser.new do |opts|
54
69
  migrate_ver = Integer(v)
55
70
  end
56
71
 
72
+ opts.on("-t", "--trace", "Output the full backtrace if an exception is raised") do
73
+ backtrace = true
74
+ end
75
+
57
76
  opts.on_tail("-v", "--version", "Show version") do
58
77
  puts "sequel #{Sequel.version}"
59
78
  exit
@@ -63,11 +82,16 @@ opts.parse!
63
82
 
64
83
  db = ARGV.shift
65
84
 
66
- if db.nil? || db.empty?
67
- puts opts
85
+ error_proc = lambda do |msg|
86
+ $stderr.puts(msg)
68
87
  exit 1
69
88
  end
70
89
 
90
+ error_proc["Error: Must specify -m if using -M"] if migrate_ver && !migrate_dir
91
+ error_proc["Error: Cannot specify -D or -d with -m"] if dump_migration && migrate_dir
92
+ error_proc["Error: Cannot specify -C with -d, -D, or -m"] if copy_databases && (dump_migration || migrate_dir)
93
+ error_proc["Error: Must specify database connection string or path to yaml file as argument"] if db.nil? || db.empty?
94
+
71
95
  if logfile || echo
72
96
  require 'logger'
73
97
  db_opts[:loggers] = []
@@ -75,30 +99,89 @@ if logfile || echo
75
99
  db_opts[:loggers] << Logger.new($stdout) if echo
76
100
  end
77
101
 
78
- if File.exist?(db)
79
- require 'yaml'
80
- env ||= "development"
81
- db_config = YAML.load_file(db)
82
- db_config = db_config[env] || db_config[env.to_sym] || db_config
83
- db_config.each{|(k,v)| db_config[k.to_sym] = db_config.delete(k)}
84
- db_config.merge!(db_opts)
102
+ connect_proc = lambda do |database|
103
+ if File.exist?(database)
104
+ require 'yaml'
105
+ env ||= "development"
106
+ db_config = YAML.load_file(database)
107
+ db_config = db_config[env] || db_config[env.to_sym] || db_config
108
+ db_config.each{|(k,v)| db_config[k.to_sym] = db_config.delete(k)}
109
+ Sequel.connect(db_config.merge!(db_opts))
110
+ else
111
+ Sequel.connect(database, db_opts)
112
+ end
85
113
  end
86
114
 
87
115
  begin
88
- DB = Sequel.connect(*(db_config ? [db_config] : [db, db_opts]))
116
+ DB = connect_proc[db]
89
117
  DB.test_connection
90
118
  if migrate_dir
91
- require 'sequel/extensions/migration'
119
+ Sequel.extension :migration
92
120
  Sequel::Migrator.apply(DB, migrate_dir, migrate_ver)
93
121
  exit
94
122
  end
123
+ if dump_migration
124
+ Sequel.extension :schema_dumper
125
+ puts DB.dump_schema_migration(:same_db=>dump_migration==:same_db)
126
+ exit
127
+ end
128
+ if copy_databases
129
+ Sequel.extension :migration, :schema_dumper
130
+
131
+ db2 = ARGV.shift
132
+ error_proc["Error: Must specify database connection string or path to yaml file as second argument for database you want to copy to"] if db2.nil? || db2.empty?
133
+ start_time = Time.now
134
+ TO_DB = connect_proc[db2]
135
+ TO_DB.test_connection
136
+ same_db = DB.database_type==TO_DB.database_type
137
+
138
+ puts "Databases connections successful"
139
+ schema_migration = eval(DB.dump_schema_migration(:indexes=>false, :same_db=>same_db))
140
+ index_migration = eval(DB.dump_indexes_migration(:same_db=>same_db))
141
+ puts "Migrations dumped successfully"
142
+
143
+ schema_migration.apply(TO_DB, :up)
144
+ puts "Tables created"
145
+
146
+ puts "Begin copying data"
147
+ DB.transaction do
148
+ TO_DB.transaction do
149
+ DB.tables.each do |table|
150
+ puts "Begin copying records for table: #{table}"
151
+ time = Time.now
152
+ to_ds = TO_DB.from(table)
153
+ j = 0
154
+ DB.from(table).each do |record|
155
+ if Time.now - time > 5
156
+ puts "Status: #{j} records copied"
157
+ time = Time.now
158
+ end
159
+ to_ds.insert(record)
160
+ j += 1
161
+ end
162
+ puts "Finished copying #{j} records for table: #{table}"
163
+ end
164
+ end
165
+ end
166
+ puts "Finished copying data"
167
+
168
+ puts "Begin creating indexes"
169
+ index_migration.apply(TO_DB, :up)
170
+ puts "Finished creating indexes"
171
+
172
+ if TO_DB.database_type == :postgres
173
+ TO_DB.tables.each{|t| TO_DB.reset_primary_key_sequence(t)}
174
+ puts "Primary key sequences reset successfully"
175
+ end
176
+ puts "Database copy finished in #{Time.now - start_time} seconds"
177
+ exit
178
+ end
95
179
  rescue => e
96
- puts "#{e.class}: #{e.message}"
97
- puts e.backtrace.first
98
- exit 1
180
+ raise e if backtrace
181
+ error_proc["Error: #{e.class}: #{e.message}#{e.backtrace.first}"]
99
182
  end
100
183
 
101
- Dir["#{load_dir}/**/*.rb"].each{|f| load(f)} if load_dir
184
+ load_dirs.each{|d| Dir["#{d}/**/*.rb"].each{|f| load(f)}}
102
185
 
103
186
  require 'irb'
104
187
  puts "Your database is stored in DB..."
@@ -0,0 +1,83 @@
1
+ = Reflection
2
+
3
+ Sequel supports reflection information in multiple ways.
4
+
5
+ == Adapter in Use
6
+
7
+ You can get the adapter in use using Database.adapter_scheme. As this is a class method, you generally need to do DB.class.adapter_scheme:
8
+
9
+ DB.class.adapter_scheme # e.g. :postgres, :jdbc, :odbc
10
+
11
+ == Database Connected To
12
+
13
+ In some case, the adapter scheme will be the same as the database to which you are connecting. However, many adapters support multiple databases. You can use the Database#database_type method to get the type of database to which you are connecting:
14
+
15
+ DB.database_type # :postgres, :h2, :mssql
16
+
17
+ == Tables in the Database
18
+
19
+ On many database types/adapters, Database#tables exists and gives an array of table name symbols:
20
+
21
+ DB.tables # [:table1, :table2, :table3, ...]
22
+
23
+ == Indexes on a table
24
+
25
+ On a few database types/adapters, Database#indexes takes a table name gives a hash of index information. Keys are index names, values are subhashes with the keys :columns and :unique :
26
+
27
+ DB.indexes(:table1) # {:index1=>{:columns=>[:column1], :unique=>false}, :index2=>{:columns=>[:column2, :column3], :unique=>true}}
28
+
29
+ Index information generally does not include partial indexes, functional indexes, or indexes on the primary key of the table.
30
+
31
+ == Column Information for a Table
32
+
33
+ Database#schema takes a table symbol and returns column information in an array with each element being an array with two elements. The first elements of the subarray is a column symbol, and the second element is a hash of information about that column. The hash should include the following keys:
34
+
35
+ * :allow_null - Whether NULL/nil is an allowed value for this column. Used by the Sequel::Model typecasting code.
36
+ * :db_type - The type of column the database provided, as a string. Used by the schema_dumper plugin for a more specific type translation.
37
+ * :default - The default value of the column, as either a string or nil. Uses a database specific format. Used by the schema_dumper plugin for converting to a ruby value.
38
+ * :primary_key - Whether this column is one of the primary key columns for the table. Used by the Sequel::Model code to determine primary key columns.
39
+ * :type - The type of column, as a symbol (e.g. :string). Used by the Sequel::Model typecasting code.
40
+
41
+ Example:
42
+
43
+ DB.schema(:table) # [[:column1, {:allow_null=>true, :db_type=>'varchar(255)', :default=>'blah', :primary_key=>false, :type=>:string}], ...]
44
+
45
+ == Column Information for a Model
46
+
47
+ Model#db_schema returns pretty much the same information, except it returns it as a hash with column keys instead of an array of two element arrays.
48
+
49
+ Model.db_schema # {:column1=>{:allow_null=>true, :db_type=>'varchar(255)', :default=>'blah', :primary_key=>false, :type=>:string}, ...}
50
+
51
+ == Columns used by a dataset/model
52
+
53
+ Dataset#columns returns the columns of the current dataset as an array of symbols:
54
+
55
+ DB[:table].columns # [:column1, :column2, :column3, ...]
56
+
57
+ Dataset#columns! does the same thing, except it ignores any cached value. In general, the cached value should never be incorrect, unless the database schema is changed after the dataset is created.
58
+
59
+ DB[:table].columns! # [:column1, :column2, :column3, ...]
60
+
61
+ Model.columns does the same thing as Dataset#columns, using the model's dataset:
62
+
63
+ Model.columns # [:column1, :column2, :column3, ...]
64
+
65
+ == Associations Defined
66
+
67
+ Sequel::Model offers complete introspection capability for all associations.
68
+
69
+ You can get an array of association symbols with Model.associations:
70
+
71
+ Model.associations # [:association1, :association2, ...]
72
+
73
+ You can get the association reflection for a single association via the Model.association_reflection. Association reflections are subclasses of hash, for ease of use and introspection (and backwards compatibility):
74
+
75
+ Model.association_reflection(:association1) # {:name=>:association1, :type=>:many_to_one, :model=>Model, :associated_class=>OtherModel, ...}
76
+
77
+ You can get an array of all association reflections via Model.all_association_reflections:
78
+
79
+ Model.all_association_reflections # [{:name=>:association1, :type=>:many_to_one, :model=>Model, :associated_class=>OtherModel, ...}, ...]
80
+
81
+ Finally, you can get a hash of association reflections via Model.association_reflections:
82
+
83
+ Model.association_reflections # {:association1=>{:name=>:association1, :type=>:many_to_one, :model=>Model, :associated_class=>OtherModel, ...}, ...}
@@ -0,0 +1,406 @@
1
+ New Plugins
2
+ -----------
3
+
4
+ 3 new plugins were added that implement features supported by
5
+ DataMapper: identity_map, tactical_eager_loading, and
6
+ lazy_attributes. These plugins don't add any real new features,
7
+ since you can do most of what they allow before simply by being
8
+ a little more explicit in your Sequel code. However, some people
9
+ prefer a less explicit approach that uses a bit more magic, and
10
+ now Sequel can accomodate them.
11
+
12
+ * The identity_map plugin allows you to create a 1-1
13
+ correspondence of model objects to database rows via a temporary
14
+ thread-local identity map. It makes the following statment true:
15
+
16
+ Sequel::Model.with_identity_map do
17
+ Album.filter{(id > 0) & (id < 2)}.first.object_id == \
18
+ Album.first(:id=>1).object_id
19
+ end
20
+
21
+ As the code above implies, you need to use the with_identity_map
22
+ method with a block to use the identity mapping feature.
23
+
24
+ By itself, identity maps don't offer much, but Sequel uses them
25
+ as a cache when looking up objects by primary key or looking up
26
+ many_to_one associated objects. Basically, it can be used as a
27
+ performance enhancer, and it also allows the support of the
28
+ lazy_attributes plugin.
29
+
30
+ The identity_map plugin is expected to be most useful in web
31
+ applications. With that in mind, here's a Rack middleware that
32
+ wraps each request in a with_identity_map call, so the
33
+ identity_map features are available inside the web app:
34
+
35
+ Sequel::Model.plugin :identity_map
36
+ class SequelIdentityMap
37
+ def initialize(app)
38
+ @app = app
39
+ end
40
+ def call(env)
41
+ Sequel::Model.with_identity_map{@app.call(env)}
42
+ end
43
+ end
44
+
45
+ * The tactical_eager_loading plugin allows you to eagerly load an
46
+ association for all models retrieved in the same group whenever
47
+ one of the models accesses the association:
48
+
49
+ # 2 queries total
50
+ Album.filter{id<100}.all do |a|
51
+ a.artists
52
+ end
53
+
54
+ In order for this correctly, you must use Dataset#all to load the
55
+ records, you cannot iterate over them via Dataset#each. This is
56
+ because eager loading requires that you have all records in
57
+ advance, and when using Dataset#each you cannot know about later
58
+ records in the dataset.
59
+
60
+ Before, you could just be explicit about the associations you
61
+ needed and make sure to eagerly load them using eager before
62
+ calling Dataset#all.
63
+
64
+ * The lazy_attributes plugin builds on the identity_map and
65
+ tactical_eager_loading plugins and allows you to create
66
+ attributes that are lazily loaded from the database:
67
+
68
+ Album.plugin :lazy_attributes, :review
69
+
70
+ This will remove the :review attribute from being selected by
71
+ default. If you try to access the attribute after it is selected,
72
+ it'll retrieve the value from the database. If the object was
73
+ retrieved with a group of other objects and an identity map is in
74
+ use, it'll retrieve the lazy attribute for the entire group of
75
+ objects at once, similar to the tatical_eager_loading plugin:
76
+
77
+ # 2 queries total
78
+ Sequel::Model.with_identity_map do
79
+ Album.filter{id<100}.all do |a|
80
+ a.review
81
+ end
82
+ end
83
+
84
+ Before, you could just set the default selected columns for a model
85
+ to not include the lazy attributes, and just use select_more to
86
+ add them to any query where the resulting model objects will
87
+ need the attributes.
88
+
89
+ * A many_through_many plugin was also added. This very powerful
90
+ plugin allows you to create associations to multiple objects through
91
+ multiple join tables. Here are some examples:
92
+
93
+ # Assume the following many to many associations:
94
+ Artist.many_to_many :albums
95
+ Album.many_to_many :tags
96
+
97
+ # Same as Artist.many_to_many :albums
98
+ Artist.many_through_many :albums,
99
+ [[:albums_artists, :artist_id, :album_id]]
100
+
101
+ # All tags associated to any album this artist is associated to
102
+ Artist.many_through_many :tags,
103
+ [[:albums_artists, :artist_id, :album_id],
104
+ [:albums, :id, :id],
105
+ [:albums_tags, :album_id, :tag_id]]
106
+
107
+ # All artists associated to any album this artist is associated to
108
+ Artist.many_through_many :artists,
109
+ [[:albums_artists, :artist_id, :album_id],
110
+ [:albums, :id, :id],
111
+ [:albums_artists, :album_id, :artist_id]]
112
+
113
+ # All albums by artists that are associated to any album this
114
+ # artist is associated to
115
+ Artist.many_through_many :artist_albums,
116
+ [[:albums_artists, :artist_id, :album_id],
117
+ [:albums, :id, :id],
118
+ [:albums_artists, :album_id, :artist_id],
119
+ [:artists, :id, :id],
120
+ [:albums_artists, :artist_id, :album_id]]
121
+
122
+ Basically, for each join table between this model and the
123
+ associated model, you use an array with a join table name, left key
124
+ name (key closer to this model), and right key name (key closer to
125
+ the associated model).
126
+
127
+ In usual Sequel fashion, this association type works not just
128
+ for single objects, but it can also be eagerly loaded via eager or
129
+ eager_graph. There are numerous additional configuration options,
130
+ please see the RDoc for details.
131
+
132
+ New bin/sequel Features
133
+ -----------------------
134
+
135
+ The bin/sequel command line tool now supports the following options:
136
+
137
+ * -C: Copies one database to another. You must specify two database
138
+ arguments. Works similar to Taps, copying the table schema, then
139
+ the table data, then creating the indexes.
140
+
141
+ * -d: Dump the schema of the database in the database-independent
142
+ migration format.
143
+
144
+ * -D: Dump the schema of the database in the database-specific
145
+ migration format.
146
+
147
+ * -h: Display the help
148
+
149
+ * -t: Output the full backtrace if an exception is raised
150
+
151
+ The bin/sequel tool is now better about checking which options can
152
+ be used together. It also now supports using the -L option multiple
153
+ times and having it load model files from multiple directory trees.
154
+
155
+ New Features
156
+ ------------
157
+
158
+ * Dataset#qualify_to and #qualify_to_first_source were added. They
159
+ allow you to qualify unqualified columns in the current dataset
160
+ to the given table or the first source. This can be used to join
161
+ a dataset that has unqualified columns to a new table which has
162
+ columns with the same name.
163
+
164
+ For example, take this dataset:
165
+
166
+ ds = DB[:albums].select(:name).order(:name).filter(:id=>1)
167
+ # SELECT name FROM albums WHERE (id = 1) ORDER BY name
168
+
169
+ Let's say you want to join it to the artists table:
170
+
171
+ ds2 = ds.join(:artists, :id=>:artist_id)
172
+ # SELECT name FROM albums
173
+ # INNER JOIN artists ON (artists.id = albums.artist_id)
174
+ # WHERE (id = 1) ORDER BY name
175
+
176
+ That's going to give you an error, as the artists table already has
177
+ columns named id and name. This new feature allows you to do the
178
+ following:
179
+
180
+ ds2 = ds.qualify_to_first_source.join(:artists, :id=>:artist_id)
181
+ # SELECT albums.name FROM albums
182
+ # INNER JOIN artists ON (artists.id = albums.artist_id)
183
+ # WHERE (albums.id = 1) ORDER BY albums.name
184
+
185
+ By doing this, all unqualified columns are qualified, so you get
186
+ a usable query. This is expected to be most useful for users that
187
+ have a default order or filter on their models and want to join
188
+ the model to another table. Before you had to replace the filters,
189
+ selection, etc. manually, or use qualified columns by default even
190
+ though the weren't needed in most cases.
191
+
192
+ * Savepoints are now supported using SQLite and MySQL, assuming you
193
+ are using a database version that supports them. You need to
194
+ pass the :savepoint option to Database#transaction to use a
195
+ savepoint.
196
+
197
+ * Model plugins can now depend on other plugins, simply by calling
198
+ the Model.plugin method inside the plugin's apply method:
199
+
200
+ module LazyAttributes
201
+ def self.apply(model)
202
+ model.plugin :tactical_eager_loading
203
+ end
204
+
205
+ * Model.plugin now takes a block with is passed to the plugin's
206
+ apply and configure method (see Backwards Compatibility section for
207
+ more information on the configure method).
208
+
209
+ * You can see which plugins are loaded for a model by using
210
+ Model.plugins.
211
+
212
+ * You can use Sequel.extension method to load extensions:
213
+
214
+ Sequel.extension :pagination, :query
215
+
216
+ This will only load extensions that ship with Sequel, unlike the
217
+ Model.plugin method which will also load external plugins.
218
+
219
+ * You can now use Database#create_table? to create the table if it
220
+ doesn't already exist (a very common need, it seems). The schema
221
+ plugin now supports Model.create_table? as well.
222
+
223
+ * #sql_subscript is now an allowed method on most SQL expression
224
+ objects that Sequel generates. Also, arguments to #sql_subscript
225
+ can now be other expressions instead of just integers.
226
+
227
+ * Associations can now take a :cartesian_product_number option, which
228
+ can be used to tell Sequel whether to turn on duplicate object
229
+ detection when eagerly loading objects through eager_graph. This
230
+ number should be 0 if the association can never create multiple
231
+ rows for each row in the current table, 1 if it can create multiple
232
+ rows in the each row in the current table, and 2 if the association
233
+ itself causes a cartesian product.
234
+
235
+ * On MySQL, Dataset#insert_ignore now affects #insert as well as
236
+ multi_insert and import.
237
+
238
+ * Database#create_table now supports an :ignore_index_errors option,
239
+ and Database#add_index now supports an :ignore_errors option.
240
+ These are used by the schema_dumper when dumping an database
241
+ schema to be restored on another database type, since indexes
242
+ aren't usually required for proper operation and some indexes
243
+ can't be transferred.
244
+
245
+ * The ADO adapter now takes a :provider option, which can be used
246
+ to set the provider.
247
+
248
+ * The ADO adapter now takes a :command_timeout option, which tells
249
+ the connection how long to wait before giving up and raising an
250
+ exception.
251
+
252
+ * The Sequel.amalgalite adapter method was added. Like the
253
+ Sequel.sqlite method, you can call it with no arguments to get
254
+ an in memory database.
255
+
256
+ Other Improvements
257
+ ------------------
258
+
259
+ * MySQL "commands out of sync" errors should no longer occur unless
260
+ you are nesting queries (calling Dataset#each inside Dataset#each).
261
+ A bug dating at least to 2007 and possibly since the initial
262
+ creation of the Sequel MySQL adapter was the cause. Before, SQL
263
+ that caused a result set that was sent using a method where Sequel
264
+ doesn't yield a result set would cause the "commands out of sync"
265
+ error on the following query. For example, the following code
266
+ would cause the error:
267
+
268
+ DB << "SHOW DATABASES"
269
+
270
+ If for some reason a "commands out of sync" error does occur,
271
+ Sequel will disconnect the connection from the connection pool,
272
+ so it won't continually stay in the pool and raise errors every
273
+ time it is used.
274
+
275
+ * The schema_dumper extension is much better about parsing defaults
276
+ from the database. It can now correctly parse most defaults on
277
+ MySQL, SQLite, and PostgreSQL databases. It no longer includes
278
+ defaults that it can't parse to a ruby object unless a database-
279
+ specific dump is requested.
280
+
281
+ * The schema_dumper extension now dumps tables in alphabetical order.
282
+
283
+ * Ordered and limited datasets are now handled correctly when using
284
+ union, intersect, and except. Also, union, intersect, and except
285
+ now always return a from_self dataset, so further limiting,
286
+ filtering, and ordering of them now works as expected.
287
+
288
+ * Dataset#graph now works correctly with a complex dataset without
289
+ having to use from_self. Before, code like the following didn't
290
+ do what was expected:
291
+
292
+ DB[:albums].
293
+ graph(DB[:artists].filter{name > 'M'}, :id=>:artist_id)
294
+
295
+ Before, the filter on DB[:artists] would be dropped. Now, Sequel
296
+ correctly uses a subselect.
297
+
298
+ * You can now specify serialization formats per column in the
299
+ serialization plugin, either by calling the plugin multiple
300
+ times or by using the new serialize_attributes method:
301
+
302
+ Album.plugin :serialization
303
+ Album.serialize_attributes :marshal, :review
304
+ Album.serialize_attributes :yaml, :name
305
+ Album.serialization_map #{:name=>:yaml, :review=>:marshal}
306
+
307
+ The public API for the serialization plugin is still backwards
308
+ compatible, but the internals have changed slightly to support
309
+ this new feature.
310
+
311
+ * You can now use eager_graph to eagerly load associations for models
312
+ that lack primary keys.
313
+
314
+ * The :eager_graph association option now works when lazily-loading
315
+ many_to_many associations.
316
+
317
+ * Dataset#add_graph_aliases now works correctly even if
318
+ set_graph_aliases hasn't been used.
319
+
320
+ * The PostgreSQL Database#tables method now assumes the public schema
321
+ if a schema is not given and there is no default_schema.
322
+
323
+ * The PostgreSQL Database#indexes method no longer returns partial
324
+ indexes or functional indexes.
325
+
326
+ * The MySQL Database#indexes method no longer returns indexes on
327
+ partial columns (prefix indexes).
328
+
329
+ * Default values for String :text=>true and File columns on MySQL
330
+ are ignored, since MySQL doesn't support them. They are not
331
+ ignored if you use text and blob, since then you are using the
332
+ database-specific syntax and Sequel doesn't do translation when
333
+ the database-specific syntax is used.
334
+
335
+ * On PostgreSQL, attempting the reset the primary key sequence for a
336
+ table without a primary key no longer causes an error.
337
+
338
+ * Using a placeholder string in an association's :condition option
339
+ now works correctly (e.g. :conditions=>['n = ?', 1])
340
+
341
+ * An error is no longer raised if you attempt to load a plugin that
342
+ has a DatasetMethods module but no public dataset methods.
343
+
344
+ * The check for dataset[n] where n is an integer was fixed. It now
345
+ raises an error inside of returning a limited dataset.
346
+
347
+ * On PostgreSQL, Dataset#insert with static SQL now works correctly.
348
+
349
+ * A reflection.rdoc file was added giving an overview of Sequel's
350
+ reflection support.
351
+
352
+ * The Migrator now works correctly with file names like
353
+ 001_12312412_file_name.rb.
354
+
355
+ * The association code now requires the classes match when looking
356
+ for a reciprocal association.
357
+
358
+ * An unlikely threading bug (race condition) was possible when using
359
+ the validation_class_methods plugin. The plugin was refactored and
360
+ now uses a mutex to avoid the issue. One of the refactoring changes
361
+ makes it so that you can no longer use a class level vaildation
362
+ inside a Class.new block (since inherited isn't called until the
363
+ block finishes).
364
+
365
+ * The exception messages when Sequel.string_to_* fail have been fixed.
366
+
367
+ * The String :text=>true generic database type has been fixed when
368
+ using the Firebird adapter.
369
+
370
+ Backwards Compatibility
371
+ -----------------------
372
+
373
+ * A plugin's apply method is now only called the first time a plugin
374
+ is loaded. Plugins can now have a configure method that is called
375
+ every time the plugin is loaded, and is always called after the
376
+ instance methods, class methods, and dataset method submodules have
377
+ been added to the model. This is different from apply, which is
378
+ called before the submodules are loaded.
379
+
380
+ If you are a plugin author, please check your implementation to
381
+ make sure this doesn't cause problems for you. If you have
382
+ questions, please post on the Sequel mailing list.
383
+
384
+ This new plugin feature will make certain things a lot easier, and
385
+ it should be mostly backwards compatible. However, if a plugin
386
+ was previously expected to be loaded multiple times with the apply
387
+ method called each time, it will no longer work correctly.
388
+
389
+ * The plugin_opts methods defined now include multiple args in an
390
+ array if multiple args are given. Before, the plugin_opts methods
391
+ just returned the first argument.
392
+
393
+ * Database#table_exists? no longer checks the cached schema
394
+ information. By default, it will always do a database query
395
+ (unless overridden in an adapter). This shouldn't affect the
396
+ results, but if were using the method a lot and expecting it to
397
+ use cached information, it doesn't have the same performance
398
+ characteristics.
399
+
400
+ * The internal storage of the :select option for datasets have
401
+ changed. You can no longer use a hash as a way of aliasing
402
+ columns. Dataset#select now does the translation from the hash to
403
+ SQL::AliasedExpression instances. Basically, if you were using
404
+ Dataset#clone directly with a :select option with hashes for
405
+ aliasing, you should switch to using Dataset#select or changing
406
+ the hashes to AliasedExpressions yourself.