sequel 2.12.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. data/CHANGELOG +62 -0
  2. data/README.rdoc +3 -3
  3. data/Rakefile +7 -0
  4. data/doc/advanced_associations.rdoc +44 -0
  5. data/doc/release_notes/3.0.0.txt +221 -0
  6. data/lib/sequel/adapters/amalgalite.rb +208 -0
  7. data/lib/sequel/adapters/db2.rb +3 -0
  8. data/lib/sequel/adapters/dbi.rb +9 -0
  9. data/lib/sequel/adapters/do.rb +0 -4
  10. data/lib/sequel/adapters/firebird.rb +16 -18
  11. data/lib/sequel/adapters/informix.rb +5 -3
  12. data/lib/sequel/adapters/jdbc.rb +24 -20
  13. data/lib/sequel/adapters/jdbc/h2.rb +15 -4
  14. data/lib/sequel/adapters/mysql.rb +4 -8
  15. data/lib/sequel/adapters/odbc.rb +0 -4
  16. data/lib/sequel/adapters/oracle.rb +0 -4
  17. data/lib/sequel/adapters/shared/mssql.rb +16 -5
  18. data/lib/sequel/adapters/shared/mysql.rb +87 -86
  19. data/lib/sequel/adapters/shared/oracle.rb +92 -3
  20. data/lib/sequel/adapters/shared/postgres.rb +85 -29
  21. data/lib/sequel/adapters/shared/progress.rb +8 -3
  22. data/lib/sequel/adapters/shared/sqlite.rb +53 -23
  23. data/lib/sequel/adapters/sqlite.rb +4 -7
  24. data/lib/sequel/adapters/utils/unsupported.rb +3 -3
  25. data/lib/sequel/connection_pool.rb +18 -25
  26. data/lib/sequel/core.rb +2 -21
  27. data/lib/sequel/database.rb +60 -44
  28. data/lib/sequel/database/schema_generator.rb +26 -31
  29. data/lib/sequel/database/schema_methods.rb +8 -3
  30. data/lib/sequel/database/schema_sql.rb +114 -28
  31. data/lib/sequel/dataset.rb +14 -41
  32. data/lib/sequel/dataset/convenience.rb +31 -54
  33. data/lib/sequel/dataset/graph.rb +7 -13
  34. data/lib/sequel/dataset/sql.rb +43 -54
  35. data/lib/sequel/extensions/inflector.rb +0 -5
  36. data/lib/sequel/extensions/schema_dumper.rb +238 -0
  37. data/lib/sequel/metaprogramming.rb +0 -20
  38. data/lib/sequel/model.rb +1 -2
  39. data/lib/sequel/model/base.rb +18 -16
  40. data/lib/sequel/model/inflections.rb +6 -9
  41. data/lib/sequel/plugins/caching.rb +0 -6
  42. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  43. data/lib/sequel/sql.rb +2 -0
  44. data/lib/sequel/version.rb +2 -2
  45. data/spec/adapters/firebird_spec.rb +35 -8
  46. data/spec/adapters/mysql_spec.rb +173 -266
  47. data/spec/adapters/oracle_spec.rb +13 -0
  48. data/spec/adapters/postgres_spec.rb +127 -227
  49. data/spec/adapters/sqlite_spec.rb +13 -171
  50. data/spec/core/connection_pool_spec.rb +15 -4
  51. data/spec/core/core_sql_spec.rb +14 -170
  52. data/spec/core/database_spec.rb +50 -132
  53. data/spec/core/dataset_spec.rb +47 -930
  54. data/spec/core/expression_filters_spec.rb +12 -0
  55. data/spec/core/schema_generator_spec.rb +37 -45
  56. data/spec/core/schema_spec.rb +26 -16
  57. data/spec/core/spec_helper.rb +0 -25
  58. data/spec/extensions/inflector_spec.rb +0 -3
  59. data/spec/extensions/schema_dumper_spec.rb +292 -0
  60. data/spec/extensions/serialization_spec.rb +9 -0
  61. data/spec/extensions/single_table_inheritance_spec.rb +6 -1
  62. data/spec/extensions/spec_helper.rb +1 -3
  63. data/spec/extensions/validation_helpers_spec.rb +4 -4
  64. data/spec/integration/database_test.rb +18 -0
  65. data/spec/integration/dataset_test.rb +112 -1
  66. data/spec/integration/eager_loader_test.rb +70 -9
  67. data/spec/integration/prepared_statement_test.rb +2 -2
  68. data/spec/integration/schema_test.rb +76 -27
  69. data/spec/integration/spec_helper.rb +0 -14
  70. data/spec/integration/transaction_test.rb +27 -0
  71. data/spec/model/associations_spec.rb +0 -36
  72. data/spec/model/base_spec.rb +18 -123
  73. data/spec/model/hooks_spec.rb +2 -235
  74. data/spec/model/inflector_spec.rb +15 -115
  75. data/spec/model/model_spec.rb +0 -120
  76. data/spec/model/plugins_spec.rb +0 -70
  77. data/spec/model/record_spec.rb +35 -93
  78. data/spec/model/spec_helper.rb +0 -27
  79. data/spec/model/validations_spec.rb +0 -931
  80. metadata +9 -14
  81. data/lib/sequel/deprecated.rb +0 -593
  82. data/lib/sequel/deprecated_migration.rb +0 -91
  83. data/lib/sequel/model/deprecated.rb +0 -204
  84. data/lib/sequel/model/deprecated_hooks.rb +0 -103
  85. data/lib/sequel/model/deprecated_inflector.rb +0 -335
  86. data/lib/sequel/model/deprecated_validations.rb +0 -388
  87. data/spec/core/core_ext_spec.rb +0 -156
  88. data/spec/core/migration_spec.rb +0 -263
  89. data/spec/core/pretty_table_spec.rb +0 -58
  90. data/spec/model/caching_spec.rb +0 -217
  91. data/spec/model/schema_spec.rb +0 -92
data/CHANGELOG CHANGED
@@ -1,3 +1,65 @@
1
+ === 3.0.0 (2009-05-04)
2
+
3
+ * Remove dead threads from connection pool if the pool is full and a connection is requested (jeremyevans)
4
+
5
+ * Add autoincrementing primary key support in the Oracle adapter, using a sequence and trigger (jeremyevans, Mike Golod)
6
+
7
+ * Make Model#save use the same server it uses for saving as for retrieving the saved record (jeremyevans)
8
+
9
+ * Add Database#database_type method, for identifying which type of database the object is connecting to (jeremyevans)
10
+
11
+ * Add ability to reset primary key sequences in the PostgreSQL adapter (jeremyevans)
12
+
13
+ * Fix parsing of non-simple sequence names (that contain uppercase, spaces, etc.) in the PostgreSQL adapter (jeremyevans)
14
+
15
+ * Support dumping indexes in the schema_dumper extension (jeremyevans)
16
+
17
+ * Add index parsing to PostgreSQL, MySQL, SQLite, and JDBC adapters (jeremyevans)
18
+
19
+ * Correctly quote SQL Array references, and handle qualified identifiers with them (e.g. :table__column.sql_subscript(1)) (jeremyevans)
20
+
21
+ * Allow dropping an index with a name different than the default name (jeremyevans)
22
+
23
+ * Allow Dataset#from to remove existing FROM tables when called without an argument, instead of raising an error later (jeremyevans)
24
+
25
+ * Fix string quoting on Oracle so it doesn't double backslashes (jeremyevans)
26
+
27
+ * Alias the count function call in Dataset#count, fixes use on MSSQL (akitaonrails, jeremyevans)
28
+
29
+ * Allow QualifiedIdentifiers to be qualified, to allow :column.qualify(:table).qualify(:schema) (jeremyevans)
30
+
31
+ * Allow :db_type=>'mssql' option to be respected when using the DBI adapter (akitaonrails)
32
+
33
+ * Add schema_dumper extension, for dumping schema of tables (jeremyevans)
34
+
35
+ * Allow generic database types specified as ruby types to take options (jeremyevans)
36
+
37
+ * Change Dataset#exclude to invert given hash argument, not negate it (jeremyevans)
38
+
39
+ * Make Dataset#filter and related methods treat multiple arguments more intuitively (jeremyevans)
40
+
41
+ * Fix full text searching with multiple search terms on MySQL (jeremyevans)
42
+
43
+ * Fix altering a column name, type, default, or NULL/NOT NULL status on MySQL (jeremyevans)
44
+
45
+ * Fix index type syntax on MySQL (jeremyevans)
46
+
47
+ * Add temporary table support, via :temp option to Database#create_table (EppO, jeremyevans)
48
+
49
+ * Add Amalgalite adapter (jeremyevans)
50
+
51
+ * Remove Sequel::Metaprogramming#metaattr_accessor and metaattr_reader (jeremyevans)
52
+
53
+ * Remove Dataset#irregular_function_sql (jeremyevans)
54
+
55
+ * Add Dataset#full_text_sql to the MySQL adapter (dusty)
56
+
57
+ * Fix schema type parsing of decimal types on MySQL (jeremyevans)
58
+
59
+ * Make Dataset#quote_identifier work with SQL::Identifiers (jeremyevans)
60
+
61
+ * Remove methods and features deprecated in 2.12.0 (jeremyevans)
62
+
1
63
  === 2.12.0 (2009-04-03)
2
64
 
3
65
  * Deprecate Java::JavaSQL::Timestamp#usec (jeremyevans)
data/README.rdoc CHANGED
@@ -11,9 +11,9 @@ Sequel is a lightweight database access toolkit for Ruby.
11
11
  configurations, and database sharding.
12
12
  * Sequel makes it easy to deal with multiple records without having
13
13
  to break your teeth on SQL.
14
- * Sequel currently has adapters for ADO, DataObjects, DB2, DBI,
15
- Firebird, Informix, JDBC, MySQL, ODBC, OpenBase, Oracle, PostgreSQL
16
- and SQLite3.
14
+ * Sequel currently has adapters for ADO, Amalgalite, DataObjects,
15
+ DB2, DBI, Firebird, Informix, JDBC, MySQL, ODBC, OpenBase, Oracle,
16
+ PostgreSQL and SQLite3.
17
17
 
18
18
  == Resources
19
19
 
data/Rakefile CHANGED
@@ -155,6 +155,13 @@ begin
155
155
  t.spec_opts = spec_opts.call
156
156
  end
157
157
 
158
+ desc "Run extention/plugin specs with coverage"
159
+ Spec::Rake::SpecTask.new("spec_plugin_cov") do |t|
160
+ t.spec_files = Dir["spec/extensions/*_spec.rb"]
161
+ t.spec_opts = spec_opts.call
162
+ t.rcov, t.rcov_opts = rcov_opts.call
163
+ end
164
+
158
165
  desc "Run integration tests"
159
166
  Spec::Rake::SpecTask.new("integration") do |t|
160
167
  t.spec_files = FileList["spec/integration/*_test.rb"]
@@ -603,3 +603,47 @@ name, with no duplicates?
603
603
  records.each{|r| r.associations[:songs].uniq!}
604
604
  end)
605
605
  end
606
+
607
+ === Statistics Associations (Sum of Associated Table Column)
608
+
609
+ In addition to getting associated records, you can use Sequel's association support
610
+ to get aggregate information for columns in associated tables (sums, averages, etc.).
611
+
612
+ Let's say you have a database with projects and tickets. A project can have many
613
+ tickets, and each ticket has a number of hours associated with it. You can use the
614
+ association support to create a Project association that gives the sum of hours for all
615
+ associated tickets.
616
+
617
+
618
+ class Project < Sequel::Model
619
+ one_to_many :tickets
620
+ many_to_one :ticket_hours, :read_only=>true, :key=>:id,
621
+ :dataset=>proc{Ticket.filter(:project_id=>id).select{sum(hours).as(hours)}},
622
+ :eager_loader=>(proc do |kh, projects, a|
623
+ projects.each{|p| p.associations[:ticket_hours] = nil}
624
+ Ticket.filter(:project_id=>kh[:id].keys).
625
+ group(:project_id).
626
+ select{[project_id, sum(hours).as(hours)]}.
627
+ all do |t|
628
+ p = kh[:id][t.values.delete(:project_id)].first
629
+ p.associations[:ticket_hours] = t
630
+ end
631
+ end)
632
+ # The association method returns a Ticket object with a single aggregate
633
+ # sum-of-hours value, but you want it to return an Integer/Float of just the
634
+ # sum of hours, so you call super and return just the sum-of-hours value.
635
+ # This works for both lazy loading and eager loading.
636
+ def ticket_hours
637
+ if s = super
638
+ s[:hours]
639
+ end
640
+ end
641
+ end
642
+ class Ticket < Sequel::Model
643
+ many_to_one :project
644
+ end
645
+
646
+ Note that it is often better to use a sum cache instead of this approach. You can implement
647
+ a sum cache using before or after save and delete hooks, or using a database trigger
648
+ (the preferred method if you only have to support one database and that database supports
649
+ triggers).
@@ -0,0 +1,221 @@
1
+ Deprecated Methods/Features Removed
2
+ -----------------------------------
3
+
4
+ Methods and features that were deprecated in 2.12.0 have been removed
5
+ in 3.0.0. Many features were moved into plugins or extensions, so in
6
+ many cases you just need to require an extension or use Model.plugin
7
+ and not make any changes to your code. See the 2.12.0 release notes
8
+ for the list of methods/features deprecated in 2.12.0.
9
+
10
+ If you are upgrading from a previous 2.x release, please upgrade to
11
+ 2.12.0 first, fix your code to remove all deprecation warnings, and
12
+ then upgrade to 3.0.0.
13
+
14
+ New Adapter
15
+ -----------
16
+
17
+ * Sequel now has an Amalgalite adapter. Amalgalite is a ruby
18
+ extension that embeds SQLite without requiring a separate SQLite
19
+ installation. The adapter is functionality complete but
20
+ significantly slower than the native SQLite adapter.
21
+
22
+ New Features
23
+ ------------
24
+
25
+ * The JDBC, PostgreSQL, MySQL, and SQLite adapters all now have a
26
+ Database#indexes method that returns indexes for a given table:
27
+
28
+ DB.indexes(:songs)
29
+ => {:songs_name_index=>{:unique=>true, :columns=>[:name]},
30
+ :songs_lyricid_index=>{:unique=>false, :columns=>[:lyricid]}}
31
+
32
+ * A schema_dumper extension was added to Sequel. It supports dumping
33
+ the schema of a table (including indexes) as a string that can be
34
+ evaluated in the context of a Database object to create the table.
35
+ It also supports dumping all tables in the database as a string
36
+ containing a Migration subclass that will rebuild the database.
37
+
38
+ require 'sequel/extensions/schema_dumper'
39
+ DB.dump_table_schema(:table)
40
+ DB.dump_schema_migration
41
+ DB.dump_schema_migration(:same_db=>true)
42
+ DB.dump_schema_migration(:indexes=>false)
43
+ DB.dump_indexes_migration
44
+
45
+ The :same_db option causes Sequel to not translate column types
46
+ to generic column types. By default, the migration created will
47
+ use generic types so it will run on other databases. However, if
48
+ you only want to support a single database, using the :same_db
49
+ option will make the migration use the exact database type parsed
50
+ from the database.
51
+
52
+ The :indexes=>false option causes indexes not be included in the
53
+ migration. The dump_indexes_migration can be used to create a
54
+ separate migration with the indexes. This can be useful if you
55
+ plan on loading a lot of data right after creating the tables,
56
+ since it is faster to add indexes after the data has been added.
57
+
58
+ * Using options with the generic database types is now supported to
59
+ a limited extent. For example, the following code now works:
60
+
61
+ DB.create_table(:table) do
62
+ String :a, :size=>50 # varchar(50)
63
+ String :b, :text=>true # text
64
+ String :c, :fixed=>true, :size=>30 # char(30)
65
+ Time :ts # timestamp
66
+ Time :t, :only_time=>true # time
67
+ end
68
+
69
+ * Using Dataset#filter and related methods with multiple arguments
70
+ now works much more intuitively:
71
+
72
+ # 2.12.0
73
+ dataset.filter(:a, :b=>1) # a IS NULL AND (b = 1) IS NULL
74
+ # 3.0.0
75
+ dataset.filter(:a, :b=>1) # a AND b = 1
76
+
77
+ * You can now create temporary tables by passing the :temp=>true
78
+ option to Database#create_table.
79
+
80
+ * The Oracle shared adapter now supports emulation of
81
+ autoincrementing primary keys by creating a sequence and a trigger,
82
+ similar to how the Firebird adapter works.
83
+
84
+ * The Database#database_type method was added that returns a symbol
85
+ specifying the database type being used. This can be different
86
+ than Database.adapter_scheme if you are using an adapter like
87
+ JDBC that allows connecting to multiple different types of
88
+ databases.
89
+
90
+ * Database#drop_index and related methods now support an options
91
+ hash that respects the :name option, so they can now be used to
92
+ drop an index that doesn't use the default index name.
93
+
94
+ * The PostgreSQL shared adapter now supports a
95
+ Database#reset_primary_key_sequence method to reset the
96
+ primary key sequence for a given table, based on code from
97
+ ActiveRecord.
98
+
99
+ * SQL::QualifiedIdentifiers can now be qualified, allowing you to do:
100
+
101
+ :column.qualify(:table).qualify(:schema)
102
+
103
+ * Using the :db_type=>'mssql' option with the DBI adapter will now
104
+ load the MSSQL support.
105
+
106
+ * The MySQL shared adapter now supports Dataset#full_text_sql, which
107
+ you can use in queries like the following:
108
+
109
+ ds.select(:table.*, ds.full_text_sql(:column, 'value').as(:ft))
110
+
111
+ Other Improvements
112
+ ------------------
113
+
114
+ * Sequel will now release connections from the connection pool
115
+ automatically if they are held by a dead thread. This can happen
116
+ if you are using MRI 1.8 and you are heavily multithreaded or
117
+ you call Thread#exit! or similar method explicitly. Those methods
118
+ skip the execution of ensure blocks which normally release the
119
+ connections when the threads exit.
120
+
121
+ * Model#save will now always use the same server when refreshing data
122
+ after an insert. This fixes an issue when Sequel's master/slave
123
+ database support is used with models.
124
+
125
+ * SQL Array references are now quoted correctly, so code like this
126
+ now works:
127
+
128
+ :table__column.sql_subscript(1)
129
+
130
+ * The PostgreSQL shared adapter now handles sequences that need to be
131
+ quoted correctly (previously these were quoted twice).
132
+
133
+ * String quoting on Oracle no longer doubles backslashes.
134
+
135
+ * Database#count now works correctly when used on MSSQL when using
136
+ an adapter that doesn't handle unnamed columns.
137
+
138
+ * Full text searching in the MySQL adapter now works correctly when
139
+ multiple search terms are used.
140
+
141
+ * Altering a column's name, type, default, or NULL/NOT NULL status
142
+ on MySQL now keeps other relevent column information. For example,
143
+ if you alter a column's type, it'll keep an existing default. This
144
+ functionality isn't complete, there may be other column information
145
+ that is lost.
146
+
147
+ * Fix creation of an index with a given type on MySQL, since MySQL's
148
+ documentation lies.
149
+
150
+ * The schema parser now handles decimal types with size specifiers,
151
+ fixing use on MySQL.
152
+
153
+ * Dataset#quote_identifier now works correctly when given an
154
+ SQL::Identifier. This allows you to do:
155
+
156
+ dataset.select{sum(hours).as(hours)}
157
+
158
+ Backwards Compatibility
159
+ -----------------------
160
+
161
+ * Sequel will now use instance_eval on all virtual row blocks without
162
+ an argument. This can lead to much nicer code:
163
+
164
+ dataset.filter{(number > 10) & (name > 'M')}
165
+ # WHERE number > 10 AND name > 'M'
166
+
167
+ 2.12.0 raised a deprecation warning if you used a virtual row block
168
+ without an argument and you hadn't set
169
+ Sequel.virtual_row_instance_eval = true.
170
+
171
+ * Dataset#exclude now inverts the given argument, instead of negating
172
+ it. This only changes its behavior if it is called with a hash or
173
+ array of all two pairs that have more than one element.
174
+
175
+ # 2.12.0
176
+ dataset.exclude(:a=>1, :b=>1) # a != 1 AND b != 1
177
+ # 3.0.0
178
+ dataset.exclude(:a=>1, :b=>1) # a != 1 OR b != 1
179
+
180
+ This was done for consistency, since exclude would only negate a
181
+ hash if it was given an argument, it would invert the same hash
182
+ if you used a block:
183
+
184
+ # 2.12.0
185
+ dataset.exclude{{:a=>1, :b=>1}} # a != 1 OR b != 1
186
+
187
+ If you want the previous behavior,
188
+ change the code to the following:
189
+
190
+ dataset.filter({:a=>1, :b=>1}.sql_negate)
191
+
192
+ * As noted above, the methods/features deprecated in 2.12.0 were
193
+ removed.
194
+
195
+ * The private Dataset#select_*_sql methods now only take a single
196
+ argument, the SQL string being built.
197
+
198
+ * Dataset#from when called without arguments would previously cause an
199
+ error to be raised when the SQL string is generated. Now it causes
200
+ no FROM clause to be used, similar to how Dataset#select with no
201
+ arguments causes SELECT * to be used.
202
+
203
+ * The internals of the generic type support and the schema generators
204
+ were changed significantly, which could have some fallout in terms
205
+ of old migrations breaking if they used the generic types and were
206
+ relying on some undocumented behavior (such as using Integer as a
207
+ type with the :unsigned option).
208
+
209
+ * The Firebird adapter no longer translates the text database
210
+ specific type. Use the following instead:
211
+
212
+ String :column, :text=>true
213
+
214
+ * The MySQL shared adapter used to use the timestamp type for Time,
215
+ now it uses datetime. This is because the timestamp type cannot
216
+ represent everything that the ruby Time class can represent.
217
+
218
+ * Metaprogramming#metaattr_accessor and #metaattr_reader methods were
219
+ removed.
220
+
221
+ * Dataset#irregular_function_sql was removed.
@@ -0,0 +1,208 @@
1
+ require 'amalgalite'
2
+ Sequel.require 'adapters/shared/sqlite'
3
+
4
+ module Sequel
5
+ # Top level module for holding all Amalgalite-related modules and classes
6
+ # for Sequel.
7
+ module Amalgalite
8
+ # Type conversion map class for Sequel's use of Amalgamite
9
+ class SequelTypeMap < ::Amalgalite::TypeMaps::DefaultMap
10
+ methods_handling_sql_types.delete('string')
11
+ methods_handling_sql_types.merge!(
12
+ 'datetime' => %w'datetime timestamp',
13
+ 'time' => %w'time',
14
+ 'float' => ['float', 'double', 'real', 'double precision'],
15
+ 'decimal' => %w'numeric decimal money'
16
+ )
17
+
18
+ # Return blobs as instances of Sequel::SQL::Blob instead of
19
+ # Amalgamite::Blob
20
+ def blob(s)
21
+ SQL::Blob.new(s)
22
+ end
23
+
24
+ # Return numeric/decimal types as instances of BigDecimal
25
+ # instead of Float
26
+ def decimal(s)
27
+ BigDecimal.new(s)
28
+ end
29
+
30
+ # Return datetime types as instances of Sequel.datetime_class
31
+ def datetime(s)
32
+ Sequel.string_to_datetime(s)
33
+ end
34
+
35
+ # Don't raise an error if the value is a string and the declared
36
+ # type doesn't match a known type, just return the value.
37
+ def result_value_of(declared_type, value)
38
+ if value.is_a?(::Amalgalite::Blob)
39
+ SQL::Blob.new(value.source)
40
+ elsif value.is_a?(String) && declared_type
41
+ (meth = self.class.sql_to_method(declared_type.downcase)) ? send(meth, value) : value
42
+ else
43
+ super
44
+ end
45
+ end
46
+ end
47
+
48
+ # Database class for SQLite databases used with Sequel and the
49
+ # amalgalite driver.
50
+ class Database < Sequel::Database
51
+ include ::Sequel::SQLite::DatabaseMethods
52
+
53
+ set_adapter_scheme :amalgalite
54
+
55
+ # Mimic the file:// uri, by having 2 preceding slashes specify a relative
56
+ # path, and 3 preceding slashes specify an absolute path.
57
+ def self.uri_to_options(uri) # :nodoc:
58
+ { :database => (uri.host.nil? && uri.path == '/') ? nil : "#{uri.host}#{uri.path}" }
59
+ end
60
+ private_class_method :uri_to_options
61
+
62
+ # Connect to the database. Since SQLite is a file based database,
63
+ # the only options available are :database (to specify the database
64
+ # name), and :timeout, to specify how long to wait for the database to
65
+ # be available if it is locked, given in milliseconds (default is 5000).
66
+ def connect(server)
67
+ opts = server_opts(server)
68
+ opts[:database] = ':memory:' if blank_object?(opts[:database])
69
+ db = ::Amalgalite::Database.new(opts[:database])
70
+ db.busy_handler(::Amalgalite::BusyTimeout.new(opts.fetch(:timeout, 5000)/50, 50))
71
+ db.type_map = SequelTypeMap.new
72
+ db
73
+ end
74
+
75
+ # Amalgalite is just the SQLite database without a separate SQLite installation.
76
+ def database_type
77
+ :sqlite
78
+ end
79
+
80
+ # Return instance of Sequel::Amalgalite::Dataset with the given options.
81
+ def dataset(opts = nil)
82
+ Amalgalite::Dataset.new(self, opts)
83
+ end
84
+
85
+ # Run the given SQL with the given arguments and reload the schema. Returns nil.
86
+ def execute_ddl(sql, opts={})
87
+ _execute(sql, opts){|conn| conn.execute_batch(sql); conn.reload_schema!}
88
+ nil
89
+ end
90
+
91
+ # Run the given SQL with the given arguments and return the number of changed rows.
92
+ def execute_dui(sql, opts={})
93
+ _execute(sql, opts){|conn| conn.execute_batch(sql); conn.row_changes}
94
+ end
95
+
96
+ # Run the given SQL with the given arguments and return the last inserted row id.
97
+ def execute_insert(sql, opts={})
98
+ _execute(sql, opts){|conn| conn.execute_batch(sql); conn.last_insert_rowid}
99
+ end
100
+
101
+ # Run the given SQL with the given arguments and yield each row.
102
+ def execute(sql, opts={})
103
+ retried = false
104
+ _execute(sql, opts) do |conn|
105
+ conn.prepare(sql) do |stmt|
106
+ begin
107
+ stmt.result_meta
108
+ rescue NoMethodError
109
+ conn.reload_schema!
110
+ stmt.result_meta
111
+ end
112
+ yield stmt
113
+ end
114
+ end
115
+ end
116
+
117
+ # Run the given SQL with the given arguments and return the first value of the first row.
118
+ def single_value(sql, opts={})
119
+ _execute(sql, opts){|conn| conn.first_value_from(sql)}
120
+ end
121
+
122
+ # Use the native driver transaction method if there isn't already a transaction
123
+ # in progress on the connection, always yielding a connection inside a transaction
124
+ # transaction.
125
+ def transaction(opts={})
126
+ synchronize(opts[:server]) do |conn|
127
+ return yield(conn) if conn.in_transaction?
128
+ begin
129
+ result = nil
130
+ log_info('Transaction.begin')
131
+ conn.transaction{result = yield(conn)}
132
+ result
133
+ rescue ::Exception => e
134
+ log_info('Transaction.rollback')
135
+ transaction_error(e, ::Amalgalite::Error, ::Amalgalite::SQLite3::Error)
136
+ ensure
137
+ log_info('Transaction.commit') unless e
138
+ end
139
+ end
140
+ end
141
+
142
+ private
143
+
144
+ # Log the SQL and yield an available connection. Rescue
145
+ # any Amalgalite::Errors and turn them into DatabaseErrors.
146
+ def _execute(sql, opts)
147
+ begin
148
+ log_info(sql)
149
+ synchronize(opts[:server]){|conn| yield conn}
150
+ rescue ::Amalgalite::Error, ::Amalgalite::SQLite3::Error => e
151
+ raise_error(e)
152
+ end
153
+ end
154
+
155
+ # The Amagalite adapter does not need the pool to convert exceptions.
156
+ # Also, force the max connections to 1 if a memory database is being
157
+ # used, as otherwise each connection gets a separate database.
158
+ def connection_pool_default_options
159
+ o = super.merge(:pool_convert_exceptions=>false)
160
+ # Default to only a single connection if a memory database is used,
161
+ # because otherwise each connection will get a separate database
162
+ o[:max_connections] = 1 if @opts[:database] == ':memory:' || blank_object?(@opts[:database])
163
+ o
164
+ end
165
+
166
+ # Disconnect given connections from the database.
167
+ def disconnect_connection(c)
168
+ c.close
169
+ end
170
+ end
171
+
172
+ # Dataset class for SQLite datasets that use the amalgalite driver.
173
+ class Dataset < Sequel::Dataset
174
+ include ::Sequel::SQLite::DatasetMethods
175
+
176
+ EXPLAIN = 'EXPLAIN %s'.freeze
177
+
178
+ # Return an array of strings specifying a query explanation for the
179
+ # current dataset.
180
+ def explain
181
+ res = []
182
+ @db.result_set(EXPLAIN % select_sql(opts), nil) {|r| res << r}
183
+ res
184
+ end
185
+
186
+ # Yield a hash for each row in the dataset.
187
+ def fetch_rows(sql)
188
+ execute(sql) do |stmt|
189
+ stmt.result_meta
190
+ @columns = cols = stmt.result_fields.map{|c| output_identifier(c)}
191
+ col_count = cols.size
192
+ stmt.each do |result|
193
+ row = {}
194
+ col_count.times{|i| row[cols[i]] = result[i]}
195
+ yield row
196
+ end
197
+ end
198
+ end
199
+
200
+ private
201
+
202
+ # Quote the string using the adapter instance method.
203
+ def literal_string(v)
204
+ "#{db.synchronize{|c| c.quote(v)}}"
205
+ end
206
+ end
207
+ end
208
+ end