sequel 3.28.0 → 3.29.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 (148) hide show
  1. data/CHANGELOG +119 -3
  2. data/Rakefile +5 -3
  3. data/bin/sequel +1 -5
  4. data/doc/model_hooks.rdoc +9 -1
  5. data/doc/opening_databases.rdoc +49 -40
  6. data/doc/prepared_statements.rdoc +27 -6
  7. data/doc/release_notes/3.28.0.txt +2 -2
  8. data/doc/release_notes/3.29.0.txt +459 -0
  9. data/doc/sharding.rdoc +7 -1
  10. data/doc/testing.rdoc +18 -9
  11. data/doc/transactions.rdoc +41 -1
  12. data/lib/sequel/adapters/ado.rb +28 -17
  13. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  14. data/lib/sequel/adapters/amalgalite.rb +11 -7
  15. data/lib/sequel/adapters/db2.rb +122 -70
  16. data/lib/sequel/adapters/dbi.rb +15 -15
  17. data/lib/sequel/adapters/do.rb +5 -36
  18. data/lib/sequel/adapters/do/mysql.rb +0 -5
  19. data/lib/sequel/adapters/do/postgres.rb +0 -5
  20. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  21. data/lib/sequel/adapters/firebird.rb +3 -6
  22. data/lib/sequel/adapters/ibmdb.rb +24 -16
  23. data/lib/sequel/adapters/informix.rb +2 -4
  24. data/lib/sequel/adapters/jdbc.rb +47 -11
  25. data/lib/sequel/adapters/jdbc/as400.rb +5 -24
  26. data/lib/sequel/adapters/jdbc/db2.rb +0 -5
  27. data/lib/sequel/adapters/jdbc/derby.rb +217 -0
  28. data/lib/sequel/adapters/jdbc/firebird.rb +0 -5
  29. data/lib/sequel/adapters/jdbc/h2.rb +10 -12
  30. data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
  31. data/lib/sequel/adapters/jdbc/informix.rb +0 -5
  32. data/lib/sequel/adapters/jdbc/jtds.rb +0 -5
  33. data/lib/sequel/adapters/jdbc/mysql.rb +0 -10
  34. data/lib/sequel/adapters/jdbc/oracle.rb +70 -3
  35. data/lib/sequel/adapters/jdbc/postgresql.rb +0 -11
  36. data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
  37. data/lib/sequel/adapters/jdbc/sqlserver.rb +0 -5
  38. data/lib/sequel/adapters/jdbc/transactions.rb +56 -7
  39. data/lib/sequel/adapters/mock.rb +315 -0
  40. data/lib/sequel/adapters/mysql.rb +64 -51
  41. data/lib/sequel/adapters/mysql2.rb +15 -9
  42. data/lib/sequel/adapters/odbc.rb +13 -6
  43. data/lib/sequel/adapters/odbc/db2.rb +0 -4
  44. data/lib/sequel/adapters/odbc/mssql.rb +0 -5
  45. data/lib/sequel/adapters/openbase.rb +2 -4
  46. data/lib/sequel/adapters/oracle.rb +333 -51
  47. data/lib/sequel/adapters/postgres.rb +80 -27
  48. data/lib/sequel/adapters/shared/access.rb +0 -6
  49. data/lib/sequel/adapters/shared/db2.rb +13 -15
  50. data/lib/sequel/adapters/shared/firebird.rb +6 -6
  51. data/lib/sequel/adapters/shared/mssql.rb +23 -18
  52. data/lib/sequel/adapters/shared/mysql.rb +6 -6
  53. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
  54. data/lib/sequel/adapters/shared/oracle.rb +185 -30
  55. data/lib/sequel/adapters/shared/postgres.rb +35 -18
  56. data/lib/sequel/adapters/shared/progress.rb +0 -6
  57. data/lib/sequel/adapters/shared/sqlite.rb +116 -37
  58. data/lib/sequel/adapters/sqlite.rb +16 -8
  59. data/lib/sequel/adapters/swift.rb +5 -5
  60. data/lib/sequel/adapters/swift/mysql.rb +0 -5
  61. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  62. data/lib/sequel/adapters/swift/sqlite.rb +6 -4
  63. data/lib/sequel/adapters/tinytds.rb +13 -10
  64. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +8 -0
  65. data/lib/sequel/core.rb +40 -0
  66. data/lib/sequel/database/connecting.rb +1 -2
  67. data/lib/sequel/database/dataset.rb +3 -3
  68. data/lib/sequel/database/dataset_defaults.rb +58 -0
  69. data/lib/sequel/database/misc.rb +62 -2
  70. data/lib/sequel/database/query.rb +113 -49
  71. data/lib/sequel/database/schema_methods.rb +7 -2
  72. data/lib/sequel/dataset/actions.rb +37 -19
  73. data/lib/sequel/dataset/features.rb +24 -0
  74. data/lib/sequel/dataset/graph.rb +7 -6
  75. data/lib/sequel/dataset/misc.rb +11 -3
  76. data/lib/sequel/dataset/mutation.rb +2 -3
  77. data/lib/sequel/dataset/prepared_statements.rb +6 -4
  78. data/lib/sequel/dataset/query.rb +46 -15
  79. data/lib/sequel/dataset/sql.rb +28 -4
  80. data/lib/sequel/extensions/named_timezones.rb +5 -0
  81. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  82. data/lib/sequel/model.rb +2 -1
  83. data/lib/sequel/model/associations.rb +115 -33
  84. data/lib/sequel/model/base.rb +91 -31
  85. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  86. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  87. data/lib/sequel/plugins/force_encoding.rb +6 -6
  88. data/lib/sequel/plugins/identity_map.rb +1 -1
  89. data/lib/sequel/plugins/many_through_many.rb +6 -10
  90. data/lib/sequel/plugins/prepared_statements.rb +12 -1
  91. data/lib/sequel/plugins/prepared_statements_associations.rb +1 -1
  92. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  93. data/lib/sequel/plugins/serialization.rb +6 -1
  94. data/lib/sequel/plugins/sharding.rb +0 -5
  95. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  96. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  97. data/lib/sequel/plugins/update_primary_key.rb +1 -1
  98. data/lib/sequel/timezones.rb +42 -42
  99. data/lib/sequel/version.rb +1 -1
  100. data/spec/adapters/mssql_spec.rb +29 -29
  101. data/spec/adapters/mysql_spec.rb +86 -104
  102. data/spec/adapters/oracle_spec.rb +48 -76
  103. data/spec/adapters/postgres_spec.rb +98 -33
  104. data/spec/adapters/spec_helper.rb +0 -5
  105. data/spec/adapters/sqlite_spec.rb +24 -21
  106. data/spec/core/connection_pool_spec.rb +9 -15
  107. data/spec/core/core_sql_spec.rb +20 -31
  108. data/spec/core/database_spec.rb +491 -227
  109. data/spec/core/dataset_spec.rb +638 -1051
  110. data/spec/core/expression_filters_spec.rb +0 -1
  111. data/spec/core/mock_adapter_spec.rb +378 -0
  112. data/spec/core/object_graph_spec.rb +48 -114
  113. data/spec/core/schema_generator_spec.rb +3 -3
  114. data/spec/core/schema_spec.rb +51 -114
  115. data/spec/core/spec_helper.rb +3 -90
  116. data/spec/extensions/class_table_inheritance_spec.rb +1 -1
  117. data/spec/extensions/dataset_associations_spec.rb +199 -0
  118. data/spec/extensions/instance_hooks_spec.rb +71 -0
  119. data/spec/extensions/named_timezones_spec.rb +22 -2
  120. data/spec/extensions/nested_attributes_spec.rb +3 -0
  121. data/spec/extensions/schema_spec.rb +1 -1
  122. data/spec/extensions/serialization_modification_detection_spec.rb +1 -0
  123. data/spec/extensions/serialization_spec.rb +5 -8
  124. data/spec/extensions/spec_helper.rb +4 -0
  125. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  126. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  127. data/spec/integration/associations_test.rb +123 -12
  128. data/spec/integration/dataset_test.rb +140 -47
  129. data/spec/integration/eager_loader_test.rb +19 -21
  130. data/spec/integration/model_test.rb +80 -1
  131. data/spec/integration/plugin_test.rb +179 -128
  132. data/spec/integration/prepared_statement_test.rb +92 -91
  133. data/spec/integration/schema_test.rb +42 -23
  134. data/spec/integration/spec_helper.rb +25 -31
  135. data/spec/integration/timezone_test.rb +38 -12
  136. data/spec/integration/transaction_test.rb +161 -34
  137. data/spec/integration/type_test.rb +3 -3
  138. data/spec/model/association_reflection_spec.rb +83 -7
  139. data/spec/model/associations_spec.rb +393 -676
  140. data/spec/model/base_spec.rb +186 -116
  141. data/spec/model/dataset_methods_spec.rb +7 -27
  142. data/spec/model/eager_loading_spec.rb +343 -867
  143. data/spec/model/hooks_spec.rb +160 -79
  144. data/spec/model/model_spec.rb +118 -165
  145. data/spec/model/plugins_spec.rb +7 -13
  146. data/spec/model/record_spec.rb +138 -207
  147. data/spec/model/spec_helper.rb +10 -73
  148. metadata +14 -8
@@ -1,9 +1,13 @@
1
+ Sequel.require 'adapters/jdbc/transactions'
2
+
1
3
  module Sequel
2
4
  module JDBC
3
5
  # Database and Dataset support for AS400 databases accessed via JDBC.
4
6
  module AS400
5
7
  # Instance methods for AS400 Database objects accessed via JDBC.
6
8
  module DatabaseMethods
9
+ include Sequel::JDBC::Transactions
10
+
7
11
  TRANSACTION_BEGIN = 'Transaction.begin'.freeze
8
12
  TRANSACTION_COMMIT = 'Transaction.commit'.freeze
9
13
  TRANSACTION_ROLLBACK = 'Transaction.rollback'.freeze
@@ -13,11 +17,6 @@ module Sequel
13
17
  :as400
14
18
  end
15
19
 
16
- # Return Sequel::JDBC::AS400::Dataset object with the given opts.
17
- def dataset(opts=nil)
18
- Sequel::JDBC::AS400::Dataset.new(self, opts)
19
- end
20
-
21
20
  # TODO: Fix for AS400
22
21
  def last_insert_id(conn, opts={})
23
22
  nil
@@ -33,25 +32,7 @@ module Sequel
33
32
  # Use JDBC connection's setAutoCommit to false to start transactions
34
33
  def begin_transaction(conn, opts={})
35
34
  set_transaction_isolation(conn, opts)
36
- log_yield(TRANSACTION_BEGIN){conn.setAutoCommit(false)}
37
- conn
38
- end
39
-
40
- # Use JDBC connection's commit method to commit transactions
41
- def commit_transaction(conn, opts={})
42
- log_yield(TRANSACTION_COMMIT){conn.commit}
43
- end
44
-
45
- # Use JDBC connection's setAutoCommit to true to enable default
46
- # auto-commit mode
47
- def remove_transaction(conn)
48
- conn.setAutoCommit(true) if conn
49
- @transactions.delete(Thread.current)
50
- end
51
-
52
- # Use JDBC connection's rollback method to rollback transactions
53
- def rollback_transaction(conn, opts={})
54
- log_yield(TRANSACTION_ROLLBACK){conn.rollback}
35
+ super
55
36
  end
56
37
  end
57
38
 
@@ -19,11 +19,6 @@ module Sequel
19
19
  include Sequel::DB2::DatabaseMethods
20
20
  include Sequel::JDBC::Transactions
21
21
 
22
- # Return instance of Sequel::JDBC::DB2::Dataset with the given opts.
23
- def dataset(opts=nil)
24
- Sequel::JDBC::DB2::Dataset.new(self, opts)
25
- end
26
-
27
22
  %w'schema_parse_table tables views indexes'.each do |s|
28
23
  class_eval("def #{s}(*a) jdbc_#{s}(*a) end", __FILE__, __LINE__)
29
24
  end
@@ -0,0 +1,217 @@
1
+ Sequel.require 'adapters/jdbc/transactions'
2
+
3
+ module Sequel
4
+ module JDBC
5
+ # Database and Dataset support for Derby databases accessed via JDBC.
6
+ module Derby
7
+ # Instance methods for Derby Database objects accessed via JDBC.
8
+ module DatabaseMethods
9
+ include ::Sequel::JDBC::Transactions
10
+
11
+ # Derby doesn't support casting integer to varchar, only integer to char,
12
+ # and char(254) appears to have the widest support (with char(255) failing).
13
+ # This does add a bunch of extra spaces at the end, but those will be trimmed
14
+ # elsewhere.
15
+ def cast_type_literal(type)
16
+ (type == String) ? 'CHAR(254)' : super
17
+ end
18
+
19
+ # Derby uses the :derby database type.
20
+ def database_type
21
+ :derby
22
+ end
23
+
24
+ # Derby uses an IDENTITY sequence for autoincrementing columns.
25
+ def serial_primary_key_options
26
+ {:primary_key => true, :type => :integer, :identity=>true, :start_with=>1}
27
+ end
28
+
29
+ # The SVN version of the database.
30
+ def svn_version
31
+ @svn_version ||= begin
32
+ v = synchronize{|c| c.get_meta_data.get_database_product_version}
33
+ v =~ /\((\d+)\)\z/
34
+ $1.to_i
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # Derby-specific syntax for renaming columns and changing a columns type/nullity.
41
+ def alter_table_sql(table, op)
42
+ case op[:op]
43
+ when :rename_column
44
+ "RENAME COLUMN #{quote_schema_table(table)}.#{quote_identifier(op[:name])} TO #{quote_identifier(op[:new_name])}"
45
+ when :set_column_type
46
+ # Derby is very limited in changing a columns type, so adding a new column and then dropping the existing column is
47
+ # the best approach, as mentioned in the Derby documentation.
48
+ temp_name = :x_sequel_temp_column_x
49
+ [alter_table_sql(table, op.merge(:op=>:add_column, :name=>temp_name)),
50
+ from(table).update_sql(temp_name=>::Sequel::SQL::Cast.new(op[:name], op[:type])),
51
+ alter_table_sql(table, op.merge(:op=>:drop_column)),
52
+ alter_table_sql(table, op.merge(:op=>:rename_column, :name=>temp_name, :new_name=>op[:name]))]
53
+ when :set_column_null
54
+ "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} #{op[:null] ? 'NULL' : 'NOT NULL'}"
55
+ else
56
+ super
57
+ end
58
+ end
59
+
60
+ # Derby doesn't allow specifying NULL for columns, only NOT NULL.
61
+ def column_definition_null_sql(sql, column)
62
+ sql << " NOT NULL" if column.fetch(:null, column[:allow_null]) == false
63
+ end
64
+
65
+ # Temporary table creation on Derby use DECLARE instead of CREATE.
66
+ def create_table_sql(name, generator, options)
67
+ if options[:temp]
68
+ "DECLARE GLOBAL TEMPORARY TABLE #{quote_identifier(name)} (#{column_list_sql(generator)}) NOT LOGGED"
69
+ else
70
+ super
71
+ end
72
+ end
73
+
74
+ # Use IDENTITY_VAL_LOCAL() to get the last inserted id.
75
+ def last_insert_id(conn, opts={})
76
+ statement(conn) do |stmt|
77
+ sql = 'SELECT IDENTITY_VAL_LOCAL() FROM sysibm.sysdummy1'
78
+ rs = log_yield(sql){stmt.executeQuery(sql)}
79
+ rs.next
80
+ rs.getInt(1)
81
+ end
82
+ end
83
+
84
+ # Derby uses RENAME TABLE syntax to rename tables.
85
+ def rename_table_sql(name, new_name)
86
+ "RENAME TABLE #{quote_schema_table(name)} TO #{quote_schema_table(new_name)}"
87
+ end
88
+
89
+ # If an :identity option is present in the column, add the necessary IDENTITY SQL.
90
+ def type_literal(column)
91
+ if column[:identity]
92
+ sql = "#{super} GENERATED BY DEFAULT AS IDENTITY"
93
+ if sw = column[:start_with]
94
+ sql << " (START WITH #{sw.to_i}"
95
+ sql << " INCREMENT BY #{column[:increment_by].to_i}" if column[:increment_by]
96
+ sql << ")"
97
+ end
98
+ sql
99
+ else
100
+ super
101
+ end
102
+ end
103
+ end
104
+
105
+ # Dataset class for Derby datasets accessed via JDBC.
106
+ class Dataset < JDBC::Dataset
107
+ BOOL_TRUE = '(1 = 1)'.freeze
108
+ BOOL_FALSE = '(1 = 0)'.freeze
109
+ SELECT_CLAUSE_METHODS = clause_methods(:select, %w'distinct columns from join where group having compounds order limit lock')
110
+
111
+ # Derby doesn't support an expression between CASE and WHEN,
112
+ # so emulate it by using an equality statement for all of the
113
+ # conditions.
114
+ def case_expression_sql(ce)
115
+ if ce.expression?
116
+ e = ce.expression
117
+ literal(::Sequel::SQL::CaseExpression.new(ce.conditions.map{|c, r| [::Sequel::SQL::BooleanExpression.new(:'=', e, c), r]}, ce.default))
118
+ else
119
+ super
120
+ end
121
+ end
122
+
123
+ # If the type is String, trim the extra spaces since CHAR is used instead
124
+ # of varchar. This can cause problems if you are casting a char/varchar to
125
+ # a string and the ending whitespace is important.
126
+ def cast_sql(expr, type)
127
+ if type == String
128
+ "RTRIM(#{super})"
129
+ else
130
+ super
131
+ end
132
+ end
133
+
134
+ # Handle Derby specific LIKE, extract, and some bitwise compliment support.
135
+ def complex_expression_sql(op, args)
136
+ case op
137
+ when :ILIKE
138
+ super(:LIKE, [SQL::Function.new(:upper, args.at(0)), SQL::Function.new(:upper, args.at(1)) ])
139
+ when :"NOT ILIKE"
140
+ super(:"NOT LIKE", [SQL::Function.new(:upper, args.at(0)), SQL::Function.new(:upper, args.at(1)) ])
141
+ when :&, :|, :^, :<<, :>>
142
+ raise Error, "Derby doesn't support the #{op} operator"
143
+ when :'B~'
144
+ "((0 - #{literal(args.at(0))}) - 1)"
145
+ when :extract
146
+ "#{args.at(0)}(#{literal(args.at(1))})"
147
+ else
148
+ super
149
+ end
150
+ end
151
+
152
+ # Derby does not support IS TRUE.
153
+ def supports_is_true?
154
+ false
155
+ end
156
+
157
+ # Derby does not support IN/NOT IN with multiple columns
158
+ def supports_multiple_column_in?
159
+ false
160
+ end
161
+
162
+ private
163
+
164
+ # Derby needs a hex string casted to BLOB for blobs.
165
+ def literal_blob(v)
166
+ blob = "CAST(X'"
167
+ v.each_byte{|x| blob << sprintf('%02x', x)}
168
+ blob << "' AS BLOB)"
169
+ blob
170
+ end
171
+
172
+ # Derby needs the standard workaround to insert all default values into
173
+ # a table with more than one column.
174
+ def insert_supports_empty_values?
175
+ false
176
+ end
177
+
178
+ # Derby uses an expression yielding false for false values.
179
+ # Newer versions can use the FALSE literal, but the latest gem version cannot.
180
+ def literal_false
181
+ BOOL_FALSE
182
+ end
183
+
184
+ # Derby handles fractional seconds in timestamps, but not in times
185
+ def literal_sqltime(v)
186
+ v.strftime("'%H:%M:%S'")
187
+ end
188
+
189
+ # Derby uses an expression yielding true for true values.
190
+ # Newer versions can use the TRUE literal, but the latest gem version cannot.
191
+ def literal_true
192
+ BOOL_TRUE
193
+ end
194
+
195
+ # Derby doesn't support common table expressions.
196
+ def select_clause_methods
197
+ SELECT_CLAUSE_METHODS
198
+ end
199
+
200
+ # Use a default FROM table if the dataset does not contain a FROM table.
201
+ def select_from_sql(sql)
202
+ if @opts[:from]
203
+ super
204
+ else
205
+ sql << " FROM sysibm.sysdummy1"
206
+ end
207
+ end
208
+
209
+ # Offset comes before limit in Derby
210
+ def select_limit_sql(sql)
211
+ sql << " OFFSET #{literal(@opts[:offset])} ROWS" if @opts[:offset]
212
+ sql << " FETCH FIRST #{literal(@opts[:limit])} ROWS ONLY" if @opts[:limit]
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
@@ -18,11 +18,6 @@ module Sequel
18
18
  @primary_keys = {}
19
19
  end
20
20
  end
21
-
22
- # Return instance of Sequel::JDBC::Firebird::Dataset with the given opts.
23
- def dataset(opts=nil)
24
- Sequel::JDBC::Firebird::Dataset.new(self, opts)
25
- end
26
21
  end
27
22
 
28
23
  # Dataset class for Firebird datasets accessed via JDBC.
@@ -17,11 +17,6 @@ module Sequel
17
17
  :h2
18
18
  end
19
19
 
20
- # Return Sequel::JDBC::H2::Dataset object with the given opts.
21
- def dataset(opts=nil)
22
- Sequel::JDBC::H2::Dataset.new(self, opts)
23
- end
24
-
25
20
  # Rollback an existing prepared transaction with the given transaction
26
21
  # identifier string.
27
22
  def rollback_prepared_transaction(transaction_id)
@@ -53,8 +48,8 @@ module Sequel
53
48
  # If the :prepare option is given and we aren't in a savepoint,
54
49
  # prepare the transaction for a two-phase commit.
55
50
  def commit_transaction(conn, opts={})
56
- if opts[:prepare] && Thread.current[:sequel_transaction_depth] <= 1
57
- log_connection_execute(conn, "PREPARE COMMIT #{opts[:prepare]}")
51
+ if (s = opts[:prepare]) && @transactions[conn][:savepoint_level] <= 1
52
+ log_connection_execute(conn, "PREPARE COMMIT #{s}")
58
53
  else
59
54
  super
60
55
  end
@@ -95,7 +90,8 @@ module Sequel
95
90
  # Use IDENTITY() to get the last inserted id.
96
91
  def last_insert_id(conn, opts={})
97
92
  statement(conn) do |stmt|
98
- rs = stmt.executeQuery('SELECT IDENTITY();')
93
+ sql = 'SELECT IDENTITY();'
94
+ rs = log_yield(sql){stmt.executeQuery(sql)}
99
95
  rs.next
100
96
  rs.getInt(1)
101
97
  end
@@ -111,7 +107,7 @@ module Sequel
111
107
  SELECT_CLAUSE_METHODS = clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
112
108
  BITWISE_METHOD_MAP = {:& =>:BITAND, :| => :BITOR, :^ => :BITXOR}
113
109
 
114
- # Work around H2's lack of a case insensitive LIKE operator
110
+ # Emulate the case insensitive LIKE operator and the bitwise operators.
115
111
  def complex_expression_sql(op, args)
116
112
  case op
117
113
  when :ILIKE
@@ -119,11 +115,13 @@ module Sequel
119
115
  when :"NOT ILIKE"
120
116
  super(:"NOT LIKE", [SQL::PlaceholderLiteralString.new("CAST(? AS VARCHAR_IGNORECASE)", [args.at(0)]), args.at(1)])
121
117
  when :&, :|, :^
122
- literal(SQL::Function.new(BITWISE_METHOD_MAP[op], *args))
118
+ complex_expression_arg_pairs(args){|a, b| literal(SQL::Function.new(BITWISE_METHOD_MAP[op], a, b))}
123
119
  when :<<
124
- "(#{literal(args[0])} * POWER(2, #{literal(args[1])}))"
120
+ complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
125
121
  when :>>
126
- "(#{literal(args[0])} / POWER(2, #{literal(args[1])}))"
122
+ complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
123
+ when :'B~'
124
+ "((0 - #{literal(args.at(0))}) - 1)"
127
125
  else
128
126
  super(op, args)
129
127
  end
@@ -0,0 +1,166 @@
1
+ Sequel.require 'adapters/jdbc/transactions'
2
+
3
+ module Sequel
4
+ module JDBC
5
+ # Database and Dataset support for HSQLDB databases accessed via JDBC.
6
+ module HSQLDB
7
+ # Instance methods for HSQLDB Database objects accessed via JDBC.
8
+ module DatabaseMethods
9
+ include ::Sequel::JDBC::Transactions
10
+
11
+ # HSQLDB uses the :hsqldb database type.
12
+ def database_type
13
+ :hsqldb
14
+ end
15
+
16
+ # HSQLDB uses an IDENTITY sequence as the default value for primary
17
+ # key columns.
18
+ def serial_primary_key_options
19
+ {:primary_key => true, :type => :integer, :identity=>true, :start_with=>1}
20
+ end
21
+
22
+ # The version of the database, as an integer (e.g 2.2.5 -> 20205)
23
+ def db_version
24
+ @db_version ||= begin
25
+ v = get{DATABASE_VERSION(){}}
26
+ if v =~ /(\d+)\.(\d+)\.(\d+)/
27
+ $1.to_i * 10000 + $2.to_i * 100 + $3.to_i
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ # HSQLDB specific SQL for renaming columns, and changing column types and/or nullity.
35
+ def alter_table_sql(table, op)
36
+ case op[:op]
37
+ when :rename_column
38
+ "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} RENAME TO #{quote_identifier(op[:new_name])}"
39
+ when :set_column_type
40
+ "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} SET DATA TYPE #{type_literal(op)}"
41
+ when :set_column_null
42
+ "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} SET #{op[:null] ? 'NULL' : 'NOT NULL'}"
43
+ else
44
+ super
45
+ end
46
+ end
47
+
48
+ # Use IDENTITY() to get the last inserted id.
49
+ def last_insert_id(conn, opts={})
50
+ statement(conn) do |stmt|
51
+ sql = 'CALL IDENTITY()'
52
+ rs = log_yield(sql){stmt.executeQuery(sql)}
53
+ rs.next
54
+ rs.getInt(1)
55
+ end
56
+ end
57
+
58
+ # If an :identity option is present in the column, add the necessary IDENTITY SQL.
59
+ # It's possible to use an IDENTITY type, but that defaults the sequence to start
60
+ # at 0 instead of 1, and we don't want that.
61
+ def type_literal(column)
62
+ if column[:identity]
63
+ sql = "#{super} GENERATED BY DEFAULT AS IDENTITY"
64
+ if sw = column[:start_with]
65
+ sql << " (START WITH #{sw.to_i}"
66
+ sql << " INCREMENT BY #{column[:increment_by].to_i}" if column[:increment_by]
67
+ sql << ")"
68
+ end
69
+ sql
70
+ else
71
+ super
72
+ end
73
+ end
74
+ end
75
+
76
+ # Dataset class for HSQLDB datasets accessed via JDBC.
77
+ class Dataset < JDBC::Dataset
78
+ BITWISE_METHOD_MAP = {:& =>:BITAND, :| => :BITOR, :^ => :BITXOR}
79
+ BOOL_TRUE = 'TRUE'.freeze
80
+ BOOL_FALSE = 'FALSE'.freeze
81
+ # HSQLDB does support common table expressions, but the support is broken.
82
+ # CTEs operate more like temprorary tables or views, lasting longer than the duration of the expression.
83
+ # CTEs in earlier queries might take precedence over CTEs with the same name in later queries.
84
+ # Also, if any CTE is recursive, all CTEs must be recursive.
85
+ # If you want to use CTEs with HSQLDB, you'll have to manually modify the dataset to allow it.
86
+ SELECT_CLAUSE_METHODS = clause_methods(:select, %w'distinct columns from join where group having compounds order limit lock')
87
+ SQL_WITH_RECURSIVE = "WITH RECURSIVE ".freeze
88
+
89
+ # Handle HSQLDB specific case insensitive LIKE and bitwise operator support.
90
+ def complex_expression_sql(op, args)
91
+ case op
92
+ when :ILIKE
93
+ super(:LIKE, [SQL::Function.new(:ucase, args.at(0)), SQL::Function.new(:ucase, args.at(1)) ])
94
+ when :"NOT ILIKE"
95
+ super(:"NOT LIKE", [SQL::Function.new(:ucase, args.at(0)), SQL::Function.new(:ucase, args.at(1)) ])
96
+ when :&, :|, :^
97
+ op = BITWISE_METHOD_MAP[op]
98
+ complex_expression_arg_pairs(args){|a, b| literal(SQL::Function.new(op, a, b))}
99
+ when :<<
100
+ complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
101
+ when :>>
102
+ complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
103
+ when :'B~'
104
+ "((0 - #{literal(args.at(0))}) - 1)"
105
+ else
106
+ super
107
+ end
108
+ end
109
+
110
+ # HSQLDB requires recursive CTEs to have column aliases.
111
+ def recursive_cte_requires_column_aliases?
112
+ true
113
+ end
114
+
115
+ # HSQLDB does not support IS TRUE.
116
+ def supports_is_true?
117
+ false
118
+ end
119
+
120
+ private
121
+
122
+ # Use string in hex format for blob data.
123
+ def literal_blob(v)
124
+ blob = "X'"
125
+ v.each_byte{|x| blob << sprintf('%02x', x)}
126
+ blob << "'"
127
+ blob
128
+ end
129
+
130
+ # HSQLDB uses FALSE for false values.
131
+ def literal_false
132
+ BOOL_FALSE
133
+ end
134
+
135
+ # HSQLDB handles fractional seconds in timestamps, but not in times
136
+ def literal_sqltime(v)
137
+ v.strftime("'%H:%M:%S'")
138
+ end
139
+
140
+ # HSQLDB uses TRUE for true values.
141
+ def literal_true
142
+ BOOL_TRUE
143
+ end
144
+
145
+ # HSQLDB does not support CTEs well enough for Sequel to enable support for them.
146
+ def select_clause_methods
147
+ SELECT_CLAUSE_METHODS
148
+ end
149
+
150
+ # Use a default FROM table if the dataset does not contain a FROM table.
151
+ def select_from_sql(sql)
152
+ if @opts[:from]
153
+ super
154
+ else
155
+ sql << " FROM (VALUES (0))"
156
+ end
157
+ end
158
+
159
+ # Use WITH RECURSIVE instead of WITH if any of the CTEs is recursive
160
+ def select_with_sql_base
161
+ opts[:with].any?{|w| w[:recursive]} ? SQL_WITH_RECURSIVE : super
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end