sequel 2.12.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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