File: hsqldb.rb

package info (click to toggle)
ruby-sequel 5.63.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 10,408 kB
  • sloc: ruby: 113,747; makefile: 3
file content (226 lines) | stat: -rw-r--r-- 7,648 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# frozen-string-literal: true

Sequel::JDBC.load_driver('org.hsqldb.jdbcDriver', :HSQLDB)
require_relative 'transactions'

module Sequel
  module JDBC
    Sequel.synchronize do
      DATABASE_SETUP[:hsqldb] = proc do |db|
        db.extend(Sequel::JDBC::HSQLDB::DatabaseMethods)
        db.dataset_class = Sequel::JDBC::HSQLDB::Dataset
        org.hsqldb.jdbcDriver
      end
    end

    module HSQLDB
      module DatabaseMethods
        include ::Sequel::JDBC::Transactions

        def database_type
          :hsqldb
        end

        def freeze
          db_version
          super
        end

        # HSQLDB uses an IDENTITY sequence as the default value for primary
        # key columns.
        def serial_primary_key_options
          {:primary_key => true, :type => :integer, :identity=>true, :start_with=>1}
        end

        # The version of the database, as an integer (e.g 2.2.5 -> 20205)
        def db_version
          return @db_version if defined?(@db_version)
          v = get(Sequel.function(:DATABASE_VERSION))
          @db_version = if v =~ /(\d+)\.(\d+)\.(\d+)/
            $1.to_i * 10000 + $2.to_i * 100 + $3.to_i
          end
        end
        
        # HSQLDB supports DROP TABLE IF EXISTS
        def supports_drop_table_if_exists?
          true
        end

        private
        
        def alter_table_sql(table, op)
          case op[:op]
          when :add_column
            if op[:table]
              [super(table, op.merge(:table=>nil)),
               alter_table_sql(table, op.merge(:op=>:add_constraint, :type=>:foreign_key, :name=>op[:foreign_key_constraint_name], :columns=>[op[:name]], :table=>op[:table]))]
            else
              super
            end
          when :rename_column
            "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} RENAME TO #{quote_identifier(op[:new_name])}"
          when :set_column_type
            "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} SET DATA TYPE #{type_literal(op)}"
          when :set_column_null
            "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} SET #{op[:null] ? 'NULL' : 'NOT NULL'}"
          else
            super
          end
        end

        # HSQLDB requires parens around the SELECT, and the WITH DATA syntax.
        def create_table_as_sql(name, sql, options)
          "#{create_table_prefix_sql(name, options)} AS (#{sql}) WITH DATA"
        end

        DATABASE_ERROR_REGEXPS = {
          /integrity constraint violation: unique constraint or index violation/ => UniqueConstraintViolation,
          /integrity constraint violation: foreign key/ => ForeignKeyConstraintViolation,
          /integrity constraint violation: check constraint/ => CheckConstraintViolation,
          /integrity constraint violation: NOT NULL check constraint/ => NotNullConstraintViolation,
          /serialization failure/ => SerializationFailure,
        }.freeze
        def database_error_regexps
          DATABASE_ERROR_REGEXPS
        end

        # IF EXISTS comes after table name on HSQLDB
        def drop_table_sql(name, options)
          "DROP TABLE #{quote_schema_table(name)}#{' IF EXISTS' if options[:if_exists]}#{' CASCADE' if options[:cascade]}"
        end
        
        # IF EXISTS comes after view name on HSQLDB
        def drop_view_sql(name, options)
          "DROP VIEW #{quote_schema_table(name)}#{' IF EXISTS' if options[:if_exists]}#{' CASCADE' if options[:cascade]}"
        end

        # Use IDENTITY() to get the last inserted id.
        def last_insert_id(conn, opts=OPTS)
          statement(conn) do |stmt|
            sql = 'CALL IDENTITY()'
            rs = log_connection_yield(sql, conn){stmt.executeQuery(sql)}
            rs.next
            rs.getLong(1)
          end
        end
        
        # Primary key indexes appear to start with sys_idx_sys_pk_ on HSQLDB
        def primary_key_index_re
          /\Asys_idx_sys_pk_/i
        end

        # If an :identity option is present in the column, add the necessary IDENTITY SQL.
        # It's possible to use an IDENTITY type, but that defaults the sequence to start
        # at 0 instead of 1, and we don't want that.
        def type_literal(column)
          if column[:identity]
            sql = "#{super} GENERATED BY DEFAULT AS IDENTITY"
            if sw = column[:start_with]
              sql += " (START WITH #{sw.to_i}"
              sql << " INCREMENT BY #{column[:increment_by].to_i}" if column[:increment_by]
              sql << ")"
            end
            sql
          else
            super
          end
        end

        # HSQLDB uses clob for text types.
        def uses_clob_for_text?
          true
        end

        # HSQLDB supports views with check option.
        def view_with_check_option_support
          :local
        end
      end
      
      class Dataset < JDBC::Dataset
        # Handle HSQLDB specific case insensitive LIKE and bitwise operator support.
        def complex_expression_sql_append(sql, op, args)
          case op
          when :ILIKE, :"NOT ILIKE"
            super(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), args.map{|v| SQL::Function.new(:ucase, v)})
          when :&, :|, :^, :%, :<<, :>>, :'B~'
            complex_expression_emulate_append(sql, op, args)
          else
            super
          end
        end

        # HSQLDB requires recursive CTEs to have column aliases.
        def recursive_cte_requires_column_aliases?
          true
        end

        # HSQLDB requires SQL standard datetimes in some places.
        def requires_sql_standard_datetimes?
          true
        end

        # HSQLDB does support common table expressions, but the support is broken.
        # CTEs operate more like temprorary tables or views, lasting longer than the duration of the expression.
        # CTEs in earlier queries might take precedence over CTEs with the same name in later queries.
        # Also, if any CTE is recursive, all CTEs must be recursive.
        # If you want to use CTEs with HSQLDB, you'll have to manually modify the dataset to allow it.
        def supports_cte?(type=:select)
          false
        end

        # HSQLDB does not support IS TRUE.
        def supports_is_true?
          false
        end

        # HSQLDB supports lateral subqueries.
        def supports_lateral_subqueries?
          true
        end

        # HSQLDB 2.3.4+ supports MERGE.  Older versions also support MERGE, but not all
        # features that are in Sequel's tests.
        def supports_merge?
          db.db_version >= 20304
        end

        private

        def empty_from_sql
          " FROM (VALUES (0))"
        end
        
        # Use string in hex format for blob data.
        def literal_blob_append(sql, v)
          sql << "X'" << v.unpack("H*").first << "'"
        end

        # HSQLDB uses FALSE for false values.
        def literal_false
          'FALSE'
        end

        # HSQLDB handles fractional seconds in timestamps, but not in times
        def literal_sqltime(v)
          v.strftime("'%H:%M:%S'")
        end

        # HSQLDB uses TRUE for true values.
        def literal_true
          'TRUE'
        end

        # HSQLDB supports multiple rows in INSERT.
        def multi_insert_sql_strategy
          :values
        end

        # Use WITH RECURSIVE instead of WITH if any of the CTEs is recursive
        def select_with_sql_base
          opts[:with].any?{|w| w[:recursive]} ? "WITH RECURSIVE " : super
        end
      end
    end
  end
end