sequel 3.28.0 → 3.29.0

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