activerecord-jdbc-alt-adapter 60.1.0-java → 61.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -35,7 +35,17 @@ ArJdbc::ConnectionMethods.module_eval do
35
35
  # * http://sqlite.org/c3ref/open.html
36
36
  # * http://sqlite.org/c3ref/c_open_autoproxy.html
37
37
  # => 0x01 = readonly, 0x40 = uri (default in JDBC)
38
- config[:properties][:open_mode] = 0x01 | 0x40
38
+ config[:properties][:open_mode] = ::SQLite3::Constants::Open::READONLY | ::SQLite3::Constants::Open::URI
39
+ end
40
+
41
+ if config[:flags]
42
+ config[:properties][:open_mode] ||= 0
43
+ config[:properties][:open_mode] |= config[:flags]
44
+
45
+ # JDBC driver has an extra flag for it
46
+ if config[:flags] & ::SQLite3::Constants::Open::SHAREDCACHE != 0
47
+ config[:properties][:shared_cache] = true
48
+ end
39
49
  end
40
50
 
41
51
  timeout = config[:timeout]
@@ -5,15 +5,17 @@ module ActiveRecord::Tasks
5
5
  DatabaseTasks.module_eval do
6
6
 
7
7
  # @override patched to adapt jdbc configuration
8
- def each_current_configuration(environment, spec_name = nil)
8
+ def each_current_configuration(environment, name = nil)
9
9
  environments = [environment]
10
10
  environments << 'test' if environment == 'development'
11
11
 
12
12
  environments.each do |env|
13
13
  ActiveRecord::Base.configurations.configs_for(env_name: env).each do |db_config|
14
- next if spec_name && spec_name != db_config.spec_name
14
+ next if name && name != db_config.name
15
15
 
16
- yield adapt_jdbc_config(db_config.config), db_config.spec_name, env unless db_config.config['database'].blank?
16
+ if db_config.database
17
+ yield adapt_jdbc_config(db_config), db_config.name, env
18
+ end
17
19
  end
18
20
  end
19
21
  end
@@ -21,21 +23,24 @@ module ActiveRecord::Tasks
21
23
  # @override patched to adapt jdbc configuration
22
24
  def each_local_configuration
23
25
  ActiveRecord::Base.configurations.configs_for.each do |db_config|
24
- next unless db_config.config['database']
26
+ next unless db_config.database
25
27
 
26
- if local_database?(db_config.config)
27
- yield adapt_jdbc_config(db_config.config)
28
+ if local_database?(db_config)
29
+ yield adapt_jdbc_config(db_config)
28
30
  else
29
- $stderr.puts "This task only modifies local databases. #{db_config.config['database']} is on a remote host."
31
+ $stderr.puts "This task only modifies local databases. #{db_config.database} is on a remote host."
30
32
  end
31
33
  end
32
34
  end
33
35
 
34
36
  private
35
37
 
36
- def adapt_jdbc_config(config)
37
- return config unless config['adapter']
38
- config.merge 'adapter' => config['adapter'].sub(/^jdbc/, '')
38
+ def adapt_jdbc_config(db_config)
39
+ if db_config.adapter.start_with? 'jdbc'
40
+ config = db_config.configuration_hash.merge(adapter: db_config.adapter.sub(/^jdbc/, ''))
41
+ db_config = ActiveRecord::DatabaseConfigurations::HashConfig.new(db_config.env_name, db_config.name, config)
42
+ end
43
+ db_config
39
44
  end
40
45
 
41
46
  end
@@ -1,18 +1,29 @@
1
- require 'active_record/tasks/database_tasks'
1
+ # frozen_string_literal: true
2
2
 
3
- require 'arjdbc/tasks/jdbc_database_tasks'
3
+ require 'active_record/tasks/database_tasks'
4
4
 
5
5
  module ArJdbc
6
- module Tasks
7
- class MSSQLDatabaseTasks < JdbcDatabaseTasks
6
+ module Tasks # :nodoc:
7
+ class MSSQLDatabaseTasks # :nodoc:
8
+ delegate :connection, to: ActiveRecord::Base
9
+ delegate :establish_connection, to: ActiveRecord::Base
8
10
  delegate :clear_active_connections!, to: ActiveRecord::Base
9
11
 
12
+ def self.using_database_configurations?
13
+ true
14
+ end
15
+
16
+ def initialize(db_config)
17
+ @db_config = db_config
18
+ @configuration_hash = db_config.configuration_hash
19
+ end
20
+
10
21
  def create
11
22
  establish_master_connection
12
- connection.create_database(configuration['database'])
13
- establish_connection configuration
14
- rescue ActiveRecord::StatementInvalid => error
15
- case error.message
23
+ connection.create_database(db_config.database, creation_options)
24
+ establish_connection(db_config)
25
+ rescue ActiveRecord::StatementInvalid => e
26
+ case e.message
16
27
  when /database .* already exists/i
17
28
  raise ActiveRecord::Tasks::DatabaseAlreadyExists
18
29
  else
@@ -22,7 +33,15 @@ module ArJdbc
22
33
 
23
34
  def drop
24
35
  establish_master_connection
25
- connection.drop_database configuration['database']
36
+ connection.drop_database(db_config.database)
37
+ end
38
+
39
+ def charset
40
+ connection.charset
41
+ end
42
+
43
+ def collation
44
+ connection.collation
26
45
  end
27
46
 
28
47
  def purge
@@ -31,40 +50,78 @@ module ArJdbc
31
50
  create
32
51
  end
33
52
 
53
+ def structure_dump(filename, _extra_flags)
54
+ args = prepare_command_options
55
+
56
+ args.concat(["-f #{filename}"])
34
57
 
35
- def structure_dump(filename)
36
- config = config_from_url_if_needed
37
- `smoscript -s #{config['host']} -d #{config['database']} -u #{config['username']} -p #{config['password']} -f #{filename} -A -U`
58
+ run_cmd('mssql-scripter', args, 'dumping')
38
59
  end
39
60
 
40
- def structure_load(filename)
41
- config = config_from_url_if_needed
42
- `sqlcmd -S #{config['host']} -d #{config['database']} -U #{config['username']} -P #{config['password']} -i #{filename}`
61
+ def structure_load(filename, _extra_flags)
62
+ args = prepare_command_options
63
+
64
+ args.concat(["-i #{filename}"])
65
+
66
+ run_cmd('mssql-cli', args, 'loading')
43
67
  end
44
68
 
45
69
  private
46
70
 
71
+ attr_reader :db_config, :configuration_hash
72
+
73
+ def creation_options
74
+ {}.tap do |options|
75
+ options[:collation] = configuration_hash[:collation] if configuration_hash.include?(:collation)
76
+
77
+ # azure creation options
78
+ options[:azure_maxsize] = configuration_hash[:azure_maxsize] if configuration_hash.include?(:azure_maxsize)
79
+ options[:azure_edition] = configuration_hash[:azure_edition] if configuration_hash.include?(:azure_edition)
80
+
81
+ if configuration_hash.include?(:azure_service_objective)
82
+ options[:azure_service_objective] = configuration_hash[:azure_service_objective]
83
+ end
84
+ end
85
+ end
86
+
47
87
  def establish_master_connection
48
- establish_connection configuration.merge('database' => 'master')
88
+ establish_connection(configuration_hash.merge(database: 'master'))
49
89
  end
50
90
 
51
- def config_from_url_if_needed
52
- config = self.config
53
- if config['url'] && ! config.key?('database')
54
- config = config_from_url(config['url'])
55
- end
56
- config
91
+ def prepare_command_options
92
+ {
93
+ server: '-S',
94
+ database: '-d',
95
+ username: '-U',
96
+ password: '-P'
97
+ }.map { |option, arg| "#{arg} #{config_for_cli[option]}" }
57
98
  end
58
99
 
59
- def deep_dup(hash)
60
- dup = hash.dup
61
- dup.each_pair do |k,v|
62
- tv = dup[k]
63
- dup[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? deep_dup(tv) : v
100
+ def config_for_cli
101
+ {}.tap do |options|
102
+ if configuration_hash[:host].present? && configuration_hash[:port].present?
103
+ options[:server] = "#{configuration_hash[:host]},#{configuration_hash[:port]}"
104
+ elsif configuration_hash[:host].present?
105
+ options[:server] = configuration_hash[:host]
106
+ end
107
+
108
+ options[:database] = configuration_hash[:database] if configuration_hash[:database].present?
109
+ options[:username] = configuration_hash[:username] if configuration_hash[:username].present?
110
+ options[:password] = configuration_hash[:password] if configuration_hash[:password].present?
64
111
  end
65
- dup
66
112
  end
67
113
 
114
+ def run_cmd(cmd, args, action)
115
+ fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args)
116
+ end
117
+
118
+ def run_cmd_error(cmd, args, action)
119
+ msg = +"failed to execute:\n"
120
+ msg << "#{cmd} #{args.join(' ')}\n\n"
121
+ msg << "Failed #{action} structure, please check the output above for any errors"
122
+ msg << " and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
123
+ msg
124
+ end
68
125
  end
69
126
 
70
127
  module DatabaseTasksMSSQL
@@ -74,8 +131,8 @@ module ArJdbc
74
131
 
75
132
  def check_protected_environments!
76
133
  super
77
- rescue ActiveRecord::JDBCError => error
78
- case error.message
134
+ rescue ActiveRecord::JDBCError => e
135
+ case e.message
79
136
  when /cannot open database .* requested by the login/i
80
137
  else
81
138
  raise
@@ -85,6 +142,6 @@ module ArJdbc
85
142
  end
86
143
  end
87
144
 
88
- ActiveRecord::Tasks::DatabaseTasks.send :include, DatabaseTasksMSSQL
145
+ ActiveRecord::Tasks::DatabaseTasks.send(:include, DatabaseTasksMSSQL)
89
146
  end
90
147
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ArJdbc
2
- VERSION = '60.1.0'
4
+ VERSION = '61.0.0'
3
5
  end
@@ -86,6 +86,7 @@ import org.jruby.anno.JRubyMethod;
86
86
  import org.jruby.exceptions.RaiseException;
87
87
  import org.jruby.ext.bigdecimal.RubyBigDecimal;
88
88
  import org.jruby.ext.date.RubyDate;
89
+ import org.jruby.ext.date.RubyDateTime;
89
90
  import org.jruby.javasupport.JavaEmbedUtils;
90
91
  import org.jruby.javasupport.JavaUtil;
91
92
  import org.jruby.runtime.Block;
@@ -124,6 +125,7 @@ public class RubyJdbcConnection extends RubyObject {
124
125
  private IRubyObject config;
125
126
  private IRubyObject adapter; // the AbstractAdapter instance we belong to
126
127
  private volatile boolean connected = true;
128
+ private RubyClass attributeClass;
127
129
 
128
130
  private boolean lazy = false; // final once set on initialize
129
131
  private boolean jndi; // final once set on initialize
@@ -132,6 +134,7 @@ public class RubyJdbcConnection extends RubyObject {
132
134
 
133
135
  protected RubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
134
136
  super(runtime, metaClass);
137
+ attributeClass = runtime.getModule("ActiveModel").getClass("Attribute");
135
138
  }
136
139
 
137
140
  private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
@@ -359,7 +362,7 @@ public class RubyJdbcConnection extends RubyObject {
359
362
  if ( ! connection.getAutoCommit() ) {
360
363
  try {
361
364
  connection.commit();
362
- resetSavepoints(context); // if any
365
+ resetSavepoints(context, connection); // if any
363
366
  return context.runtime.newBoolean(true);
364
367
  }
365
368
  finally {
@@ -380,7 +383,7 @@ public class RubyJdbcConnection extends RubyObject {
380
383
  if ( ! connection.getAutoCommit() ) {
381
384
  try {
382
385
  connection.rollback();
383
- resetSavepoints(context); // if any
386
+ resetSavepoints(context, connection); // if any
384
387
  return context.tru;
385
388
  } finally {
386
389
  connection.setAutoCommit(true);
@@ -516,7 +519,7 @@ public class RubyJdbcConnection extends RubyObject {
516
519
  return null;
517
520
  }
518
521
 
519
- protected boolean resetSavepoints(final ThreadContext context) {
522
+ protected boolean resetSavepoints(final ThreadContext context, final Connection connection) throws SQLException {
520
523
  if ( hasInternalVariable("savepoints") ) {
521
524
  removeInternalVariable("savepoints");
522
525
  return true;
@@ -610,11 +613,7 @@ public class RubyJdbcConnection extends RubyObject {
610
613
  return convertJavaToRuby( connection.unwrap(Connection.class) );
611
614
  }
612
615
  }
613
- catch (AbstractMethodError e) {
614
- debugStackTrace(context, e);
615
- warn(context, "driver/pool connection does not support unwrapping: " + e);
616
- }
617
- catch (SQLException e) {
616
+ catch (AbstractMethodError | SQLException e) {
618
617
  debugStackTrace(context, e);
619
618
  warn(context, "driver/pool connection does not support unwrapping: " + e);
620
619
  }
@@ -860,27 +859,25 @@ public class RubyJdbcConnection extends RubyObject {
860
859
  */
861
860
  @JRubyMethod(name = "execute_insert_pk", required = 2)
862
861
  public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject pk) {
863
- return withConnection(context, new Callable<IRubyObject>() {
864
- public IRubyObject call(final Connection connection) throws SQLException {
865
- Statement statement = null;
866
- final String query = sqlString(sql);
867
- try {
868
-
869
- statement = createStatement(context, connection);
862
+ return withConnection(context, connection -> {
863
+ Statement statement = null;
864
+ final String query = sqlString(sql);
865
+ try {
870
866
 
871
- if (pk == context.nil || pk == context.fals || !supportsGeneratedKeys(connection)) {
872
- statement.executeUpdate(query, Statement.RETURN_GENERATED_KEYS);
873
- } else {
874
- statement.executeUpdate(query, createStatementPk(pk));
875
- }
867
+ statement = createStatement(context, connection);
876
868
 
877
- return mapGeneratedKeys(context, connection, statement);
878
- } catch (final SQLException e) {
879
- debugErrorSQL(context, query);
880
- throw e;
881
- } finally {
882
- close(statement);
869
+ if (pk == context.nil || pk == context.fals || !supportsGeneratedKeys(connection)) {
870
+ statement.executeUpdate(query, Statement.RETURN_GENERATED_KEYS);
871
+ } else {
872
+ statement.executeUpdate(query, createStatementPk(pk));
883
873
  }
874
+
875
+ return mapGeneratedKeys(context, connection, statement);
876
+ } catch (final SQLException e) {
877
+ debugErrorSQL(context, query);
878
+ throw e;
879
+ } finally {
880
+ close(statement);
884
881
  }
885
882
  });
886
883
  }
@@ -903,26 +900,24 @@ public class RubyJdbcConnection extends RubyObject {
903
900
  @JRubyMethod(name = "execute_insert_pk", required = 3)
904
901
  public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject binds,
905
902
  final IRubyObject pk) {
906
- return withConnection(context, new Callable<IRubyObject>() {
907
- public IRubyObject call(final Connection connection) throws SQLException {
908
- PreparedStatement statement = null;
909
- final String query = sqlString(sql);
910
- try {
911
- if (pk == context.nil || pk == context.fals || !supportsGeneratedKeys(connection)) {
912
- statement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
913
- } else {
914
- statement = connection.prepareStatement(query, createStatementPk(pk));
915
- }
916
-
917
- setStatementParameters(context, connection, statement, (RubyArray) binds);
918
- statement.executeUpdate();
919
- return mapGeneratedKeys(context, connection, statement);
920
- } catch (final SQLException e) {
921
- debugErrorSQL(context, query);
922
- throw e;
923
- } finally {
924
- close(statement);
903
+ return withConnection(context, connection -> {
904
+ PreparedStatement statement = null;
905
+ final String query = sqlString(sql);
906
+ try {
907
+ if (pk == context.nil || pk == context.fals || !supportsGeneratedKeys(connection)) {
908
+ statement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
909
+ } else {
910
+ statement = connection.prepareStatement(query, createStatementPk(pk));
925
911
  }
912
+
913
+ setStatementParameters(context, connection, statement, (RubyArray) binds);
914
+ statement.executeUpdate();
915
+ return mapGeneratedKeys(context, connection, statement);
916
+ } catch (final SQLException e) {
917
+ debugErrorSQL(context, query);
918
+ throw e;
919
+ } finally {
920
+ close(statement);
926
921
  }
927
922
  });
928
923
  }
@@ -1012,12 +1007,12 @@ public class RubyJdbcConnection extends RubyObject {
1012
1007
  binds = null;
1013
1008
  } else { // (sql, binds)
1014
1009
  maxRows = 0;
1015
- binds = (RubyArray) TypeConverter.checkArrayType(args[1]);
1010
+ binds = (RubyArray) TypeConverter.checkArrayType(context, args[1]);
1016
1011
  }
1017
1012
  break;
1018
1013
  case 3: // (sql, max_rows, binds)
1019
1014
  maxRows = RubyNumeric.fix2int(args[1]);
1020
- binds = (RubyArray) TypeConverter.checkArrayType(args[2]);
1015
+ binds = (RubyArray) TypeConverter.checkArrayType(context, args[2]);
1021
1016
  break;
1022
1017
  default: // (sql) 1-arg
1023
1018
  maxRows = 0;
@@ -1106,6 +1101,28 @@ public class RubyJdbcConnection extends RubyObject {
1106
1101
  });
1107
1102
  }
1108
1103
 
1104
+ @JRubyMethod(required = 1)
1105
+ public IRubyObject get_first_value(final ThreadContext context, final IRubyObject sql) {
1106
+ return withConnection(context, connection -> {
1107
+ Statement statement = null;
1108
+ final String query = sqlString(sql);
1109
+ try {
1110
+ statement = createStatement(context, connection);
1111
+ statement.execute(query);
1112
+ ResultSet rs = statement.getResultSet();
1113
+ if (rs == null || !rs.next()) return context.nil;
1114
+
1115
+ return jdbcToRuby(context, context.getRuntime(), 1, rs.getMetaData().getColumnType(1), rs);
1116
+
1117
+ } catch (final SQLException e) {
1118
+ debugErrorSQL(context, query);
1119
+ throw e;
1120
+ } finally {
1121
+ close(statement);
1122
+ }
1123
+ });
1124
+ }
1125
+
1109
1126
  /**
1110
1127
  * Prepares a query, returns a wrapped PreparedStatement. This takes care of exception wrapping
1111
1128
  * @param context which context this method is executing on.
@@ -2402,9 +2419,16 @@ public class RubyJdbcConnection extends RubyObject {
2402
2419
  final Connection connection, final PreparedStatement statement,
2403
2420
  final int index, IRubyObject attribute) throws SQLException {
2404
2421
 
2405
- //debugMessage(context, attribute);
2406
- final int type = jdbcTypeForAttribute(context, attribute);
2407
- IRubyObject value = valueForDatabase(context, attribute);
2422
+ final IRubyObject value;
2423
+ final int type;
2424
+
2425
+ if (attributeClass.isInstance(attribute)) {
2426
+ type = jdbcTypeForAttribute(context, attribute);
2427
+ value = valueForDatabase(context, attribute);
2428
+ } else {
2429
+ type = jdbcTypeForPrimitiveAttribute(context, attribute);
2430
+ value = attribute;
2431
+ }
2408
2432
 
2409
2433
  // All the set methods were calling this first so save a method call in the nil case
2410
2434
  if ( value == context.nil ) {
@@ -2520,6 +2544,34 @@ public class RubyJdbcConnection extends RubyObject {
2520
2544
  return Types.OTHER; // -1 as well as 0 are used in Types
2521
2545
  }
2522
2546
 
2547
+ protected String internedTypeForPrimitive(final ThreadContext context, final IRubyObject value) throws SQLException {
2548
+ if (value instanceof RubyString) {
2549
+ return "string";
2550
+ }
2551
+ if (value instanceof RubyInteger) {
2552
+ return "integer";
2553
+ }
2554
+ if (value instanceof RubyNumeric) {
2555
+ return "float";
2556
+ }
2557
+ if (value instanceof RubyTime || value instanceof RubyDateTime) {
2558
+ return "timestamp";
2559
+ }
2560
+ if (value instanceof RubyDate) {
2561
+ return "date";
2562
+ }
2563
+ if (value instanceof RubyBoolean) {
2564
+ return "boolean";
2565
+ }
2566
+ return "string";
2567
+ }
2568
+
2569
+ protected Integer jdbcTypeForPrimitiveAttribute(final ThreadContext context,
2570
+ final IRubyObject attribute) throws SQLException {
2571
+ final String internedType = internedTypeForPrimitive(context, attribute);
2572
+ return jdbcTypeFor(internedType);
2573
+ }
2574
+
2523
2575
  protected Integer jdbcTypeFor(final String type) {
2524
2576
  return JDBC_TYPE_FOR.get(type);
2525
2577
  }
@@ -2531,7 +2583,9 @@ public class RubyJdbcConnection extends RubyObject {
2531
2583
  }
2532
2584
 
2533
2585
  protected static IRubyObject attributeSQLType(final ThreadContext context, final IRubyObject attribute) {
2534
- return attributeType(context, attribute).callMethod(context, "type");
2586
+ final IRubyObject type = attributeType(context, attribute);
2587
+ if (type != null) return type.callMethod(context, "type");
2588
+ return context.nil;
2535
2589
  }
2536
2590
 
2537
2591
  private final CachingCallSite value_site = new FunctionalCachingCallSite("value"); // AR::Attribute#value
@@ -2546,23 +2600,7 @@ public class RubyJdbcConnection extends RubyObject {
2546
2600
 
2547
2601
  final IRubyObject value = value_site.call(context, attribute, attribute);
2548
2602
 
2549
- if (value instanceof RubyInteger) {
2550
- return "integer";
2551
- }
2552
-
2553
- if (value instanceof RubyNumeric) {
2554
- return "float";
2555
- }
2556
-
2557
- if (value instanceof RubyTime) {
2558
- return "timestamp";
2559
- }
2560
-
2561
- if (value instanceof RubyBoolean) {
2562
- return "boolean";
2563
- }
2564
-
2565
- return "string";
2603
+ return internedTypeForPrimitive(context, value);
2566
2604
  }
2567
2605
 
2568
2606
  // to be overriden in child class for database specific types