# frozen-string-literal: true Sequel::JDBC.load_driver('Java::OrgH2::Driver', :H2) require_relative '../../extensions/auto_cast_date_and_time' module Sequel module JDBC Sequel.synchronize do DATABASE_SETUP[:h2] = proc do |db| db.extend(Sequel::JDBC::H2::DatabaseMethods) db.dataset_class = Sequel::JDBC::H2::Dataset Java::OrgH2::Driver end end module H2 module DatabaseMethods include AutoCastDateAndTime def commit_prepared_transaction(transaction_id, opts=OPTS) run("COMMIT TRANSACTION #{transaction_id}", opts) end def database_type :h2 end def freeze h2_version version2? super end def h2_version @h2_version ||= get(Sequel.function(:H2VERSION)) end def rollback_prepared_transaction(transaction_id, opts=OPTS) run("ROLLBACK TRANSACTION #{transaction_id}", opts) end # H2 uses an IDENTITY type for primary keys def serial_primary_key_options {:primary_key => true, :type => :identity, :identity=>true} end # H2 supports CREATE TABLE IF NOT EXISTS syntax def supports_create_table_if_not_exists? true end # H2 supports prepared transactions def supports_prepared_transactions? true end # H2 supports savepoints def supports_savepoints? true end private # H2 does not allow adding primary key constraints to NULLable columns. def can_add_primary_key_constraint_on_nullable_columns? false end # If the :prepare option is given and we aren't in a savepoint, # prepare the transaction for a two-phase commit. def commit_transaction(conn, opts=OPTS) if (s = opts[:prepare]) && savepoint_level(conn) <= 1 log_connection_execute(conn, "PREPARE COMMIT #{s}") else super end end def alter_table_sql(table, op) case op[:op] when :add_column if (pk = op.delete(:primary_key)) || (ref = op.delete(:table)) if pk op[:null] = false end sqls = [super(table, op)] if pk && (h2_version >= '1.4' || op[:type] != :identity) # H2 needs to add a primary key column as a constraint in this case sqls << "ALTER TABLE #{quote_schema_table(table)} ADD PRIMARY KEY (#{quote_identifier(op[:name])})" end if ref op[:table] = ref constraint_name = op[:foreign_key_constraint_name] sqls << "ALTER TABLE #{quote_schema_table(table)} ADD#{" CONSTRAINT #{quote_identifier(constraint_name)}" if constraint_name} FOREIGN KEY (#{quote_identifier(op[:name])}) #{column_references_sql(op)}" end sqls else super(table, op) 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_null "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} SET#{' NOT' unless op[:null]} NULL" when :set_column_type if sch = schema(table) if cs = sch.each{|k, v| break v if k == op[:name]; nil} cs = cs.dup cs[:default] = cs[:ruby_default] op = cs.merge!(op) end end sql = "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} #{type_literal(op)}".dup column_definition_order.each{|m| send(:"column_definition_#{m}_sql", sql, op)} sql when :drop_constraint if op[:type] == :primary_key "ALTER TABLE #{quote_schema_table(table)} DROP PRIMARY KEY" else super(table, op) end else super(table, op) end end # Default to a single connection for a memory database. def connection_pool_default_options o = super uri == 'jdbc:h2:mem:' ? o.merge(:max_connections=>1) : o end DATABASE_ERROR_REGEXPS = { /Unique index or primary key violation/ => UniqueConstraintViolation, /Referential integrity constraint violation/ => ForeignKeyConstraintViolation, /Check constraint violation/ => CheckConstraintViolation, /NULL not allowed for column/ => NotNullConstraintViolation, /Deadlock detected\. The current transaction was rolled back\./ => SerializationFailure, }.freeze def database_error_regexps DATABASE_ERROR_REGEXPS end def execute_statement_insert(stmt, sql) stmt.executeUpdate(sql, JavaSQL::Statement::RETURN_GENERATED_KEYS) end def prepare_jdbc_statement(conn, sql, opts) opts[:type] == :insert ? conn.prepareStatement(sql, JavaSQL::Statement::RETURN_GENERATED_KEYS) : super end # Get the last inserted id using getGeneratedKeys, scope_identity, or identity. def last_insert_id(conn, opts=OPTS) if stmt = opts[:stmt] rs = stmt.getGeneratedKeys begin if rs.next begin rs.getLong(1) rescue rs.getObject(1) rescue nil end end ensure rs.close end elsif !version2? statement(conn) do |stmt| sql = 'SELECT IDENTITY()' rs = log_connection_yield(sql, conn){stmt.executeQuery(sql)} rs.next rs.getLong(1) end end end def primary_key_index_re /\Aprimary_key/i end # H2 does not support named column constraints. def supports_named_column_constraints? false end # Use BIGINT IDENTITY for identity columns that use :Bignum type def type_literal_generic_bignum_symbol(column) column[:identity] ? 'BIGINT AUTO_INCREMENT' : super end def version2? return @version2 if defined?(@version2) @version2 = h2_version.to_i >= 2 end end class Dataset < JDBC::Dataset ILIKE_PLACEHOLDER = ["CAST(".freeze, " AS VARCHAR_IGNORECASE)".freeze].freeze # Emulate the case insensitive LIKE operator and the bitwise operators. def complex_expression_sql_append(sql, op, args) case op when :ILIKE, :"NOT ILIKE" super(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), [SQL::PlaceholderLiteralString.new(ILIKE_PLACEHOLDER, [args[0]]), args[1]]) when :&, :|, :^, :<<, :>>, :'B~' complex_expression_emulate_append(sql, op, args) else super end end # H2 does not support derived column lists def supports_derived_column_lists? false end # H2 requires SQL standard datetimes def requires_sql_standard_datetimes? true end # H2 doesn't support IS TRUE def supports_is_true? false end # H2 doesn't support JOIN USING def supports_join_using? false end # H2 supports MERGE def supports_merge? true end # H2 doesn't support multiple columns in IN/NOT IN def supports_multiple_column_in? false end private # H2 expects hexadecimal strings for blob values def literal_blob_append(sql, v) if db.send(:version2?) super else sql << "'" << v.unpack("H*").first << "'" end end def literal_false 'FALSE' end def literal_true 'TRUE' end # H2 handles fractional seconds in timestamps, but not in times def literal_sqltime(v) v.strftime("'%H:%M:%S'") end # H2 supports multiple rows in INSERT. def multi_insert_sql_strategy :values end def select_only_offset_sql(sql) if db.send(:version2?) super else sql << " LIMIT -1 OFFSET " literal_append(sql, @opts[:offset]) end end # H2 supports quoted function names. def supports_quoted_function_names? true end end end end end