sequel 3.27.0 → 3.28.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/CHANGELOG +96 -0
  2. data/README.rdoc +2 -2
  3. data/Rakefile +1 -1
  4. data/doc/association_basics.rdoc +48 -0
  5. data/doc/opening_databases.rdoc +29 -5
  6. data/doc/prepared_statements.rdoc +1 -0
  7. data/doc/release_notes/3.28.0.txt +304 -0
  8. data/doc/testing.rdoc +42 -0
  9. data/doc/transactions.rdoc +97 -0
  10. data/lib/sequel/adapters/db2.rb +95 -65
  11. data/lib/sequel/adapters/firebird.rb +25 -219
  12. data/lib/sequel/adapters/ibmdb.rb +440 -0
  13. data/lib/sequel/adapters/jdbc.rb +12 -0
  14. data/lib/sequel/adapters/jdbc/as400.rb +0 -7
  15. data/lib/sequel/adapters/jdbc/db2.rb +49 -0
  16. data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
  18. data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
  19. data/lib/sequel/adapters/mysql.rb +10 -15
  20. data/lib/sequel/adapters/odbc.rb +1 -2
  21. data/lib/sequel/adapters/odbc/db2.rb +5 -5
  22. data/lib/sequel/adapters/postgres.rb +71 -11
  23. data/lib/sequel/adapters/shared/db2.rb +290 -0
  24. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +18 -75
  26. data/lib/sequel/adapters/shared/mysql.rb +13 -0
  27. data/lib/sequel/adapters/shared/postgres.rb +52 -36
  28. data/lib/sequel/adapters/shared/sqlite.rb +32 -36
  29. data/lib/sequel/adapters/sqlite.rb +4 -8
  30. data/lib/sequel/adapters/tinytds.rb +7 -3
  31. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
  32. data/lib/sequel/core.rb +1 -1
  33. data/lib/sequel/database/connecting.rb +1 -1
  34. data/lib/sequel/database/misc.rb +6 -5
  35. data/lib/sequel/database/query.rb +1 -1
  36. data/lib/sequel/database/schema_generator.rb +2 -1
  37. data/lib/sequel/dataset/actions.rb +149 -33
  38. data/lib/sequel/dataset/features.rb +44 -7
  39. data/lib/sequel/dataset/misc.rb +9 -1
  40. data/lib/sequel/dataset/prepared_statements.rb +2 -2
  41. data/lib/sequel/dataset/query.rb +63 -10
  42. data/lib/sequel/dataset/sql.rb +22 -5
  43. data/lib/sequel/model.rb +3 -3
  44. data/lib/sequel/model/associations.rb +250 -27
  45. data/lib/sequel/model/base.rb +10 -16
  46. data/lib/sequel/plugins/many_through_many.rb +34 -2
  47. data/lib/sequel/plugins/prepared_statements_with_pk.rb +1 -1
  48. data/lib/sequel/sql.rb +94 -51
  49. data/lib/sequel/version.rb +1 -1
  50. data/spec/adapters/db2_spec.rb +146 -0
  51. data/spec/adapters/postgres_spec.rb +74 -6
  52. data/spec/adapters/spec_helper.rb +1 -0
  53. data/spec/adapters/sqlite_spec.rb +11 -0
  54. data/spec/core/database_spec.rb +7 -0
  55. data/spec/core/dataset_spec.rb +180 -17
  56. data/spec/core/expression_filters_spec.rb +107 -41
  57. data/spec/core/spec_helper.rb +11 -0
  58. data/spec/extensions/many_through_many_spec.rb +115 -1
  59. data/spec/extensions/prepared_statements_with_pk_spec.rb +3 -3
  60. data/spec/integration/associations_test.rb +193 -15
  61. data/spec/integration/database_test.rb +4 -2
  62. data/spec/integration/dataset_test.rb +215 -19
  63. data/spec/integration/plugin_test.rb +8 -5
  64. data/spec/integration/prepared_statement_test.rb +91 -98
  65. data/spec/integration/schema_test.rb +27 -11
  66. data/spec/integration/spec_helper.rb +10 -0
  67. data/spec/integration/type_test.rb +2 -2
  68. data/spec/model/association_reflection_spec.rb +91 -0
  69. data/spec/model/associations_spec.rb +13 -0
  70. data/spec/model/base_spec.rb +8 -21
  71. data/spec/model/eager_loading_spec.rb +243 -9
  72. data/spec/model/model_spec.rb +15 -2
  73. metadata +16 -4
@@ -62,3 +62,45 @@ The order in which you delete/truncate the tables is important if you are using
62
62
  [:table1, :table2].each{|x| Sequel::Model.db.from(x).delete}
63
63
  end
64
64
  end
65
+
66
+ = Testing Sequel Itself
67
+
68
+ Sequel has multiple separate test suites. All test suites run under either RSpec 1 or RSpec 2.
69
+
70
+ == rake spec
71
+
72
+ The +spec+ rake task (which is also the default rake task) runs Sequel's core and model specs. These specs use a mocked database connection, and test for specific SQL used and for generally correct behavior.
73
+
74
+ == rake spec_plugin
75
+
76
+ The +spec_plugin+ rake task runs the specs for the plugins and extensions that ship with Sequel. These also use a mocked database connection, and operate very similarly to the general Sequel core and model specs.
77
+
78
+ == rake spec_<i>adapter</i> (e.g. rake spec_postgres)
79
+
80
+ The <tt>spec_<i>adapter</i></tt> specs run against a real database connection with nothing mocked, and test for correct results. They are much slower than the standard specs, but they will catch errors that are mocked out by the default specs, as well show issues that only occur on a certain database, adapter, interpreter, or some combination of those.
81
+
82
+ These specs are broken down into two parts. For each database, there are specific specs that only apply to that database, and these are called the adapter specs. There are also shared specs that apply to all (or almost all) databases, these are called the integration specs.
83
+
84
+ == Environment variables
85
+
86
+ Sequel often uses environment variables when testing to specify either the database to be tested or specify how testing should be done.
87
+
88
+ === Connection Strings
89
+
90
+ The following environment variables specify Database connection URL strings:
91
+
92
+ * SEQUEL_INTEGRATION_URL: integration specs
93
+ * SEQUEL_FB_SPEC_DB: firebird adapter specs
94
+ * SEQUEL_MSSQL_SPEC_DB: mssql adapter specs
95
+ * SEQUEL_PG_SPEC_DB: postgres adapter specs
96
+ * SEQUEL_SQLITE_SPEC_DB: sqlite adapter specs
97
+ * SEQUEL_MY_SPEC_DB: mysql adapter specs
98
+ * SEQUEL_DB2_SPEC_DB: db2 adapter specs
99
+
100
+ === Other
101
+
102
+ * SEQUEL_MSSQL_SPEC_REQUIRE: Separate file to require when running mssql adapter specs
103
+ * SEQUEL_DB2_SPEC_REQUIRE: Separate file to require when running db2 adapter specs
104
+ * SEQUEL_COLUMNS_INTROSPECTION: Whehter to run the specs with the columns_introspection extension loaded by default
105
+ * SEQUEL_NO_PENDING: Don't mark any specs as pending, try running all specs
106
+ * SKIPPED_TEST_WARN: Warn when skipping any tests because libraries aren't available
@@ -0,0 +1,97 @@
1
+ = Database Transactions
2
+
3
+ Sequel uses autocommit mode by default for all of its database adapters, so in general in Sequel if you want to use database transactions, you need to be explicit about it. There are a few cases where transactions are used implicitly by default:
4
+
5
+ * Dataset#import to insert many records at once
6
+ * Model#save
7
+ * Model#destroy
8
+ * Migrations
9
+ * A few model plugins
10
+
11
+ Everywhere else, it is up to use to use a database transaction if you want to.
12
+
13
+ == Basic Transaction Usage
14
+
15
+ In Sequel, the <tt>Database#transaction</tt> method should be called if you want to use a database transaction. This method must be called with a block. If the block does not raise an exception, the transaction is committed:
16
+
17
+ DB.transaction do # BEGIN
18
+ DB[:foo].insert(1) # INSERT
19
+ end # COMMIT
20
+
21
+ If the block raises a Sequel::Rollback exception, the transaction is rolled back, but no exception is raised outside the block:
22
+
23
+ DB.transaction do # BEGIN
24
+ raise Sequel::Rollback
25
+ end # ROLLBACK
26
+ # no exception raised
27
+
28
+ If any other exception is raised, the transaction is rolled back, and the exception is raised outside the block:
29
+
30
+ # ArgumentError raised
31
+ DB.transaction do # BEGIN
32
+ raise ArgumentError
33
+ end # ROLLBACK
34
+ # ArgumentError raised
35
+
36
+ == Nested Transaction Calls / Savepoints
37
+
38
+ You can nest calls to transaction, which by default just reuses the existing transaction:
39
+
40
+ DB.transaction do # BEGIN
41
+ DB.transaction do
42
+ DB[:foo].insert(1) # INSERT
43
+ end
44
+ end # COMMIT
45
+
46
+ You can use the <tt>:savepoint => true</tt> option in the inner transaction to explicitly use a savepoint (if the database supports it):
47
+
48
+ DB.transaction do # BEGIN
49
+ DB.transaction(:savepoint => true) do # SAVEPOINT
50
+ DB[:foo].insert(1) # INSERT
51
+ end # RELEASE SAVEPOINT
52
+ end # COMMIT
53
+
54
+ If a Sequel::Rollback exception is raised inside the savepoint block, it will only rollback to the savepoint:
55
+
56
+ DB.transaction do # BEGIN
57
+ DB.transaction(:savepoint => true) do # SAVEPOINT
58
+ raise Sequel::Rollback
59
+ end # ROLLBACK TO SAVEPOINT
60
+ # no exception raised
61
+ end # COMMIT
62
+
63
+ Other exceptions, unless rescued inside the outer transaction block, will rollback the savepoint and the outer transactions, since they are reraised by the transaction code:
64
+
65
+ DB.transaction do # BEGIN
66
+ DB.transaction(:savepoint => true) do # SAVEPOINT
67
+ raise ArgumentError
68
+ end # ROLLBACK TO SAVEPOINT
69
+ end # ROLLBACK
70
+ # ArgumentError raised
71
+
72
+ == Prepared Transactions / Two-Phase Commit
73
+
74
+ Sequel supports database prepared transactions on PostreSQL, MySQL, and H2. With prepared transactions, at the end of the transaction, the transaction is not immediately committed (it acts like a rollback). Later, you can call +commit_prepared_transaction+ to commit the transaction or +rollback_prepared_transaction+ to roll the transaction back. Prepared transactions are usually used with distributed databases to make sure all databases commit the same transaction or none of them do.
75
+
76
+ To use prepared transactions in Sequel, you provide a string as the value of the :prepare option:
77
+
78
+ DB.transaction(:prepare => 'foo') do # BEGIN
79
+ DB[:foo].insert(1) # INSERT
80
+ end # PREPARE TRANSACTION 'foo'
81
+
82
+ Later, you can commit the prepared transaction:
83
+
84
+ DB.commit_prepared_transaction('foo')
85
+
86
+ or roll the prepared transaction back:
87
+
88
+ DB.rollback_prepared_transaction('foo')
89
+
90
+ == Transaction Isolation Levels
91
+
92
+ The SQL standard supports 4 isolation levels: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, and SERIALIZABLE. Not all databases implement the levels as specified in the standard (or implement the levels at all), but on PostgreSQL, MySQL, and Microsoft SQL Server, you can specify which transaction isolation level you want to use via the :isolation option to <tt>Database#transaction</tt>. The isolation level is specified as one of the following symbols: :uncommitted, :committed, :repeatable, and :serializable. Using this option make Sequel use the correct transaction isolation syntax for your database:
93
+
94
+ DB.transaction(:isolation => :serializable) do # BEGIN
95
+ # SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
96
+ DB[:foo].insert(1) # INSERT
97
+ end # COMMIT
@@ -1,23 +1,24 @@
1
1
  require 'db2/db2cli'
2
+ Sequel.require %w'shared/db2', 'adapters'
2
3
 
3
4
  module Sequel
4
5
  module DB2
5
6
  class Database < Sequel::Database
6
- set_adapter_scheme :db2
7
+ include DatabaseMethods
7
8
 
8
- include DB2CLI
9
+ set_adapter_scheme :db2
9
10
 
10
11
  TEMPORARY = 'GLOBAL TEMPORARY '.freeze
11
12
 
12
- rc, @@env = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE)
13
+ rc, @@env = DB2CLI.SQLAllocHandle(DB2CLI::SQL_HANDLE_ENV, DB2CLI::SQL_NULL_HANDLE)
13
14
  #check_error(rc, "Could not allocate DB2 environment")
14
15
 
15
16
  def connect(server)
16
17
  opts = server_opts(server)
17
- rc, dbc = SQLAllocHandle(SQL_HANDLE_DBC, @@env)
18
+ rc, dbc = DB2CLI.SQLAllocHandle(DB2CLI::SQL_HANDLE_DBC, @@env)
18
19
  check_error(rc, "Could not allocate database connection")
19
20
 
20
- rc = SQLConnect(dbc, opts[:database], opts[:user], opts[:password])
21
+ rc = DB2CLI.SQLConnect(dbc, opts[:database], opts[:user], opts[:password])
21
22
  check_error(rc, "Could not connect to database")
22
23
 
23
24
  dbc
@@ -32,109 +33,138 @@ module Sequel
32
33
  DB2::Dataset.new(self, opts)
33
34
  end
34
35
 
35
- def execute(sql, opts={})
36
+ def execute(sql, opts={}, &block)
37
+ synchronize(opts[:server]){|conn| log_connection_execute(conn, sql, &block)}
38
+ end
39
+ alias do execute
40
+
41
+ def execute_insert(sql, opts={})
36
42
  synchronize(opts[:server]) do |conn|
37
- rc, sth = SQLAllocHandle(SQL_HANDLE_STMT, @handle)
38
- check_error(rc, "Could not allocate statement")
39
-
40
- begin
41
- rc = log_yield(sql){SQLExecDirect(sth, sql)}
42
- check_error(rc, "Could not execute statement")
43
-
44
- yield(sth) if block_given?
45
-
46
- rc, rpc = SQLRowCount(sth)
47
- check_error(rc, "Could not get RPC")
48
- rpc
49
- ensure
50
- rc = SQLFreeHandle(SQL_HANDLE_STMT, sth)
51
- check_error(rc, "Could not free statement")
43
+ log_connection_execute(conn, sql)
44
+ sql = "SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1"
45
+ log_connection_execute(conn, sql) do |sth|
46
+ rc, name, buflen, datatype, size, digits, nullable = DB2CLI.SQLDescribeCol(sth, 1, 256)
47
+ check_error(rc, "Could not describe column")
48
+ if (rc = DB2CLI.SQLFetch(sth)) != DB2CLI::SQL_NO_DATA_FOUND
49
+ rc, v = DB2CLI.SQLGetData(sth, 1, datatype, size)
50
+ check_error(rc, "Could not get data")
51
+ if v.is_a?(String)
52
+ return v.to_i
53
+ else
54
+ return nil
55
+ end
56
+ end
52
57
  end
53
58
  end
54
59
  end
55
- alias_method :do, :execute
56
-
57
- private
58
60
 
59
61
  def check_error(rc, msg)
60
62
  case rc
61
- when SQL_SUCCESS, SQL_SUCCESS_WITH_INFO
63
+ when DB2CLI::SQL_SUCCESS, DB2CLI::SQL_SUCCESS_WITH_INFO
62
64
  nil
63
65
  else
64
66
  raise DatabaseError, msg
65
67
  end
66
68
  end
67
69
 
70
+ private
71
+
72
+ def begin_transaction(conn, opts={})
73
+ log_yield(TRANSACTION_BEGIN){DB2CLI.SQLSetConnectAttr(conn, DB2CLI::SQL_ATTR_AUTOCOMMIT, DB2CLI::SQL_AUTOCOMMIT_OFF)}
74
+ conn
75
+ end
76
+
77
+ def rollback_transaction(conn, opts={})
78
+ log_yield(TRANSACTION_ROLLBACK){DB2CLI.SQLEndTran(DB2CLI::SQL_HANDLE_DBC, conn, DB2CLI::SQL_ROLLBACK)}
79
+ ensure
80
+ DB2CLI.SQLSetConnectAttr(conn, DB2CLI::SQL_ATTR_AUTOCOMMIT, DB2CLI::SQL_AUTOCOMMIT_ON)
81
+ end
82
+
83
+ def commit_transaction(conn, opts={})
84
+ log_yield(TRANSACTION_COMMIT){DB2CLI.SQLEndTran(DB2CLI::SQL_HANDLE_DBC, conn, DB2CLI::SQL_COMMIT)}
85
+ ensure
86
+ DB2CLI.SQLSetConnectAttr(conn, DB2CLI::SQL_ATTR_AUTOCOMMIT, DB2CLI::SQL_AUTOCOMMIT_ON)
87
+ end
88
+
89
+ def log_connection_execute(conn, sql)
90
+ rc, sth = DB2CLI.SQLAllocHandle(DB2CLI::SQL_HANDLE_STMT, conn)
91
+ check_error(rc, "Could not allocate statement")
92
+
93
+ begin
94
+ rc = log_yield(sql){DB2CLI.SQLExecDirect(sth, sql)}
95
+ check_error(rc, "Could not execute statement: #{sql}")
96
+
97
+ yield(sth) if block_given?
98
+
99
+ rc, rpc = DB2CLI.SQLRowCount(sth)
100
+ check_error(rc, "Could not get RPC")
101
+ rpc
102
+ ensure
103
+ rc = DB2CLI.SQLFreeHandle(DB2CLI::SQL_HANDLE_STMT, sth)
104
+ check_error(rc, "Could not free statement")
105
+ end
106
+ end
107
+
68
108
  def disconnect_connection(conn)
69
- rc = SQLDisconnect(conn)
109
+ rc = DB2CLI.SQLDisconnect(conn)
70
110
  check_error(rc, "Could not disconnect from database")
71
111
 
72
- rc = SQLFreeHandle(SQL_HANDLE_DBC, conn)
112
+ rc = DB2CLI.SQLFreeHandle(DB2CLI::SQL_HANDLE_DBC, conn)
73
113
  check_error(rc, "Could not free Database handle")
74
114
  end
75
115
  end
76
116
 
77
117
  class Dataset < Sequel::Dataset
118
+ include DatasetMethods
119
+
78
120
  MAX_COL_SIZE = 256
79
121
 
80
122
  def fetch_rows(sql)
81
123
  execute(sql) do |sth|
82
- @column_info = get_column_info(sth)
83
- @columns = @column_info.map {|c| output_identifier(c[:name])}
84
- while (rc = SQLFetch(@handle)) != SQL_NO_DATA_FOUND
85
- @db.check_error(rc, "Could not fetch row")
86
- yield hash_row(sth)
124
+ offset = @opts[:offset]
125
+ db = @db
126
+ i = 1
127
+ column_info = get_column_info(sth)
128
+ cols = column_info.map{|c| c.at(1)}
129
+ cols.delete(row_number_column) if offset
130
+ @columns = cols
131
+ while (rc = DB2CLI.SQLFetch(sth)) != DB2CLI::SQL_NO_DATA_FOUND
132
+ db.check_error(rc, "Could not fetch row")
133
+ row = {}
134
+ column_info.each do |i, c, t, s|
135
+ rc, v = DB2CLI.SQLGetData(sth, i, t, s)
136
+ db.check_error(rc, "Could not get data")
137
+ row[c] = convert_type(v)
138
+ end
139
+ row.delete(row_number_column) if offset
140
+ yield row
87
141
  end
88
142
  end
89
143
  self
90
144
  end
91
145
 
92
- # DB2 supports window functions
93
- def supports_window_functions?
94
- true
95
- end
96
-
97
146
  private
98
147
 
99
- # Modify the sql to limit the number of rows returned
100
- def select_limit_sql(sql)
101
- if l = @opts[:limit]
102
- sql << " FETCH FIRST #{l == 1 ? 'ROW' : "#{literal(l)} ROWS"} ONLY"
103
- end
104
- end
105
-
106
148
  def get_column_info(sth)
107
- rc, column_count = SQLNumResultCols(sth)
108
- @db.check_error(rc, "Could not get number of result columns")
149
+ db = @db
150
+ rc, column_count = DB2CLI.SQLNumResultCols(sth)
151
+ db.check_error(rc, "Could not get number of result columns")
109
152
 
110
153
  (1..column_count).map do |i|
111
- rc, name, buflen, datatype, size, digits, nullable = SQLDescribeCol(sth, i, MAX_COL_SIZE)
112
- @b.check_error(rc, "Could not describe column")
113
-
114
- {:name => name, :db2_type => datatype, :precision => size}
154
+ rc, name, buflen, datatype, size, digits, nullable = DB2CLI.SQLDescribeCol(sth, i, MAX_COL_SIZE)
155
+ db.check_error(rc, "Could not describe column")
156
+ [i, output_identifier(name), datatype, size]
115
157
  end
116
158
  end
117
159
 
118
- def hash_row(sth)
119
- row = {}
120
- @column_info.each_with_index do |c, i|
121
- rc, v = SQLGetData(sth, i+1, c[:db2_type], c[:precision])
122
- @db.check_error(rc, "Could not get data")
123
-
124
- row[output_identifier(c[:name])] = convert_type(v)
125
- end
126
- row
127
- end
128
-
129
160
  def convert_type(v)
130
161
  case v
131
162
  when DB2CLI::Date
132
- DBI::Date.new(v.year, v.month, v.day)
163
+ Date.new(v.year, v.month, v.day)
133
164
  when DB2CLI::Time
134
- DBI::Time.new(v.hour, v.minute, v.second)
165
+ Sequel::SQLTime.create(v.hour, v.minute, v.second)
135
166
  when DB2CLI::Timestamp
136
- DBI::Timestamp.new(v.year, v.month, v.day,
137
- v.hour, v.minute, v.second, v.fraction)
167
+ Sequel.database_to_application_timestamp(v.to_s)
138
168
  when DB2CLI::Null
139
169
  nil
140
170
  else
@@ -1,109 +1,64 @@
1
1
  require 'fb'
2
+ Sequel.require 'adapters/shared/firebird'
2
3
 
3
4
  module Sequel
4
5
  # The Sequel Firebird adapter requires the ruby fb driver located at
5
6
  # http://github.com/wishdev/fb.
6
7
  module Firebird
7
8
  class Database < Sequel::Database
9
+ include Sequel::Firebird::DatabaseMethods
10
+
8
11
  set_adapter_scheme :firebird
9
12
 
10
- AUTO_INCREMENT = ''.freeze
11
- TEMPORARY = 'GLOBAL TEMPORARY '.freeze
13
+ DISCONNECT_ERRORS = /Unsuccessful execution caused by a system error that precludes successful execution of subsequent statements/
12
14
 
13
- # Add the primary_keys and primary_key_sequences instance variables,
15
+ # Add the primary_keys instance variables.
14
16
  # so we can get the correct return values for inserted rows.
15
17
  def initialize(*args)
16
18
  super
17
19
  @primary_keys = {}
18
- @primary_key_sequences = {}
19
20
  end
20
21
 
21
22
  def connect(server)
22
23
  opts = server_opts(server)
23
24
 
24
- db = Fb::Database.new(
25
+ Fb::Database.new(
25
26
  :database => "#{opts[:host]}:#{opts[:database]}",
26
27
  :username => opts[:user],
27
- :password => opts[:password])
28
- conn = db.connect
29
- conn.downcase_names = true
30
- conn
31
- end
32
-
33
- def create_trigger(*args)
34
- self << create_trigger_sql(*args)
28
+ :password => opts[:password]).connect
35
29
  end
36
30
 
37
31
  def dataset(opts = nil)
38
32
  Firebird::Dataset.new(self, opts)
39
33
  end
40
34
 
41
- def drop_sequence(name)
42
- self << drop_sequence_sql(name)
43
- end
44
-
45
35
  def execute(sql, opts={})
46
36
  begin
47
37
  synchronize(opts[:server]) do |conn|
38
+ if conn.transaction_started && !@transactions.include?(Thread.current)
39
+ conn.rollback
40
+ raise DatabaseDisconnectError, "transaction accidently left open, rolling back and disconnecting"
41
+ end
48
42
  r = log_yield(sql){conn.execute(sql)}
49
43
  yield(r) if block_given?
50
44
  r
51
45
  end
52
- rescue => e
53
- raise_error(e, :classes=>[Fb::Error])
46
+ rescue Fb::Error => e
47
+ raise_error(e, :disconnect=>DISCONNECT_ERRORS.match(e.message))
54
48
  end
55
49
  end
56
50
 
57
- # Return primary key for the given table.
58
- def primary_key(table, server=nil)
59
- synchronize(server){|conn| primary_key_for_table(conn, table)}
60
- end
61
-
62
- # Returns primary key for the given table. This information is
63
- # cached, and if the primary key for a table is changed, the
64
- # @primary_keys instance variable should be reset manually.
65
- def primary_key_for_table(conn, table)
66
- @primary_keys[quote_identifier(table)] ||= conn.table_primary_key(quote_identifier(table))
67
- end
68
-
69
- def restart_sequence(*args)
70
- self << restart_sequence_sql(*args)
71
- end
72
-
73
- def sequences(opts={})
74
- ds = self[:"rdb$generators"].server(opts[:server]).filter(:"rdb$system_flag" => 0).select(:"rdb$generator_name")
75
- block_given? ? yield(ds) : ds.map{|r| ds.send(:output_identifier, r[:"rdb$generator_name"])}
76
- end
77
-
78
- def tables(opts={})
79
- ds = self[:"rdb$relations"].server(opts[:server]).filter(:"rdb$view_blr" => nil, Sequel::SQL::Function.new(:COALESCE, :"rdb$system_flag", 0) => 0).select(:"rdb$relation_name")
80
- block_given? ? yield(ds) : ds.map{|r| ds.send(:output_identifier, r[:"rdb$relation_name"])}
81
- end
82
-
83
51
  private
84
52
 
85
- # Use Firebird specific syntax for add column
86
- def alter_table_sql(table, op)
87
- case op[:op]
88
- when :add_column
89
- "ALTER TABLE #{quote_schema_table(table)} ADD #{column_definition_sql(op)}"
90
- when :drop_column
91
- "ALTER TABLE #{quote_schema_table(table)} DROP #{column_definition_sql(op)}"
92
- when :rename_column
93
- "ALTER TABLE #{quote_schema_table(table)} ALTER #{quote_identifier(op[:name])} TO #{quote_identifier(op[:new_name])}"
94
- when :set_column_type
95
- "ALTER TABLE #{quote_schema_table(table)} ALTER #{quote_identifier(op[:name])} TYPE #{type_literal(op)}"
96
- else
97
- super(table, op)
98
- end
99
- end
100
-
101
- def auto_increment_sql()
102
- AUTO_INCREMENT
103
- end
104
-
105
53
  def begin_transaction(conn, opts={})
106
- log_yield(TRANSACTION_BEGIN){conn.transaction}
54
+ log_yield(TRANSACTION_BEGIN) do
55
+ begin
56
+ conn.transaction
57
+ rescue Fb::Error => e
58
+ conn.rollback
59
+ raise_error(e, :disconnect=>true)
60
+ end
61
+ end
107
62
  conn
108
63
  end
109
64
 
@@ -111,60 +66,6 @@ module Sequel
111
66
  log_yield(TRANSACTION_COMMIT){conn.commit}
112
67
  end
113
68
 
114
- def create_sequence_sql(name, opts={})
115
- "CREATE SEQUENCE #{quote_identifier(name)}"
116
- end
117
-
118
- # Firebird gets an override because of the mess of creating a
119
- # sequence and trigger for auto-incrementing primary keys.
120
- def create_table_from_generator(name, generator, options)
121
- drop_statement, create_statements = create_table_sql_list(name, generator, options)
122
- (execute_ddl(drop_statement) rescue nil) if drop_statement
123
- create_statements.each{|sql| execute_ddl(sql)}
124
- end
125
-
126
- def create_table_sql_list(name, generator, options={})
127
- statements = [create_table_sql(name, generator, options)]
128
- drop_seq_statement = nil
129
- generator.columns.each do |c|
130
- if c[:auto_increment]
131
- c[:sequence_name] ||= "seq_#{name}_#{c[:name]}"
132
- unless c[:create_sequence] == false
133
- drop_seq_statement = drop_sequence_sql(c[:sequence_name])
134
- statements << create_sequence_sql(c[:sequence_name])
135
- statements << restart_sequence_sql(c[:sequence_name], {:restart_position => c[:sequence_start_position]}) if c[:sequence_start_position]
136
- end
137
- unless c[:create_trigger] == false
138
- c[:trigger_name] ||= "BI_#{name}_#{c[:name]}"
139
- c[:quoted_name] = quote_identifier(c[:name])
140
- trigger_definition = <<-END
141
- begin
142
- if ((new.#{c[:quoted_name]} is null) or (new.#{c[:quoted_name]} = 0)) then
143
- begin
144
- new.#{c[:quoted_name]} = next value for #{c[:sequence_name]};
145
- end
146
- end
147
- END
148
- statements << create_trigger_sql(name, c[:trigger_name], trigger_definition, {:events => [:insert]})
149
- end
150
- end
151
- end
152
- [drop_seq_statement, statements]
153
- end
154
-
155
- def create_trigger_sql(table, name, definition, opts={})
156
- events = opts[:events] ? Array(opts[:events]) : [:insert, :update, :delete]
157
- whence = opts[:after] ? 'AFTER' : 'BEFORE'
158
- inactive = opts[:inactive] ? 'INACTIVE' : 'ACTIVE'
159
- position = opts.fetch(:position, 0)
160
- sql = <<-end_sql
161
- CREATE TRIGGER #{quote_identifier(name)} for #{quote_identifier(table)}
162
- #{inactive} #{whence} #{events.map{|e| e.to_s.upcase}.join(' OR ')} position #{position}
163
- as #{definition}
164
- end_sql
165
- sql
166
- end
167
-
168
69
  def database_error_classes
169
70
  [Fb::Error]
170
71
  end
@@ -173,42 +74,24 @@ module Sequel
173
74
  c.close
174
75
  end
175
76
 
176
- def drop_sequence_sql(name)
177
- "DROP SEQUENCE #{quote_identifier(name)}"
178
- end
179
-
180
- def restart_sequence_sql(name, opts={})
181
- seq_name = quote_identifier(name)
182
- "ALTER SEQUENCE #{seq_name} RESTART WITH #{opts[:restart_position]}"
183
- end
184
-
185
77
  def rollback_transaction(conn, opts={})
186
78
  log_yield(TRANSACTION_ROLLBACK){conn.rollback}
187
79
  end
188
-
189
- def type_literal_generic_string(column)
190
- column[:text] ? :"BLOB SUB_TYPE TEXT" : super
191
- end
192
80
  end
193
81
 
194
82
  # Dataset class for Firebird datasets
195
83
  class Dataset < Sequel::Dataset
196
- BOOL_TRUE = '1'.freeze
197
- BOOL_FALSE = '0'.freeze
198
- NULL = LiteralString.new('NULL').freeze
199
- COMMA_SEPARATOR = ', '.freeze
200
- SELECT_CLAUSE_METHODS = clause_methods(:select, %w'with distinct limit columns from join where group having compounds order')
201
- INSERT_CLAUSE_METHODS = clause_methods(:insert, %w'into columns values returning_select')
84
+ include Sequel::Firebird::DatasetMethods
202
85
 
203
86
  # Yield all rows returned by executing the given SQL and converting
204
87
  # the types.
205
88
  def fetch_rows(sql)
206
89
  execute(sql) do |s|
207
90
  begin
208
- @columns = s.fields.map{|c| output_identifier(c.name)}
209
- s.fetchall(:symbols_hash).each do |r|
91
+ @columns = columns = s.fields.map{|c| output_identifier(c.name)}
92
+ s.fetchall.each do |r|
210
93
  h = {}
211
- r.each{|k,v| h[output_identifier(k)] = v}
94
+ r.zip(columns).each{|v, c| h[c] = v}
212
95
  yield h
213
96
  end
214
97
  ensure
@@ -217,83 +100,6 @@ module Sequel
217
100
  end
218
101
  self
219
102
  end
220
-
221
- # Insert given values into the database.
222
- def insert(*values)
223
- if !@opts[:sql]
224
- clone(default_server_opts(:sql=>insert_returning_pk_sql(*values))).single_value
225
- else
226
- execute_insert(insert_sql(*values), :table=>opts[:from].first,
227
- :values=>values.size == 1 ? values.first : values)
228
- end
229
- end
230
-
231
- # Use the RETURNING clause to return the primary key of the inserted record, if it exists
232
- def insert_returning_pk_sql(*values)
233
- pk = db.primary_key(opts[:from].first)
234
- insert_returning_sql(pk ? Sequel::SQL::Identifier.new(pk) : NULL, *values)
235
- end
236
-
237
- # Use the RETURNING clause to return the columns listed in returning.
238
- def insert_returning_sql(returning, *values)
239
- "#{insert_sql(*values)} RETURNING #{column_list(Array(returning))}"
240
- end
241
-
242
- # Insert a record returning the record inserted
243
- def insert_select(*values)
244
- naked.clone(default_server_opts(:sql=>insert_returning_sql(nil, *values))).single_record
245
- end
246
-
247
- def requires_sql_standard_datetimes?
248
- true
249
- end
250
-
251
- def supports_insert_select?
252
- true
253
- end
254
-
255
- # Firebird does not support INTERSECT or EXCEPT
256
- def supports_intersect_except?
257
- false
258
- end
259
-
260
- private
261
-
262
- def insert_clause_methods
263
- INSERT_CLAUSE_METHODS
264
- end
265
-
266
- def insert_returning_select_sql(sql)
267
- if opts.has_key?(:returning)
268
- sql << " RETURNING #{column_list(Array(opts[:returning]))}"
269
- end
270
- end
271
-
272
- def hash_row(stmt, row)
273
- @columns.inject({}) do |m, c|
274
- m[c] = row.shift
275
- m
276
- end
277
- end
278
-
279
- def literal_false
280
- BOOL_FALSE
281
- end
282
-
283
- def literal_true
284
- BOOL_TRUE
285
- end
286
-
287
- # The order of clauses in the SELECT SQL statement
288
- def select_clause_methods
289
- SELECT_CLAUSE_METHODS
290
- end
291
-
292
- def select_limit_sql(sql)
293
- sql << " FIRST #{@opts[:limit]}" if @opts[:limit]
294
- sql << " SKIP #{@opts[:offset]}" if @opts[:offset]
295
- end
296
-
297
103
  end
298
104
  end
299
105
  end