upsert 2.9.10-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.ruby-version +1 -0
  4. data/.standard.yml +1 -0
  5. data/.travis.yml +63 -0
  6. data/.yardopts +2 -0
  7. data/CHANGELOG +265 -0
  8. data/Gemfile +20 -0
  9. data/LICENSE +24 -0
  10. data/README.md +411 -0
  11. data/Rakefile +54 -0
  12. data/lib/upsert.rb +284 -0
  13. data/lib/upsert/active_record_upsert.rb +12 -0
  14. data/lib/upsert/binary.rb +8 -0
  15. data/lib/upsert/column_definition.rb +79 -0
  16. data/lib/upsert/column_definition/mysql.rb +24 -0
  17. data/lib/upsert/column_definition/postgresql.rb +66 -0
  18. data/lib/upsert/column_definition/sqlite3.rb +34 -0
  19. data/lib/upsert/connection.rb +37 -0
  20. data/lib/upsert/connection/Java_ComMysqlJdbc_JDBC4Connection.rb +31 -0
  21. data/lib/upsert/connection/Java_OrgPostgresqlJdbc_PgConnection.rb +33 -0
  22. data/lib/upsert/connection/Java_OrgSqlite_Conn.rb +17 -0
  23. data/lib/upsert/connection/Mysql2_Client.rb +76 -0
  24. data/lib/upsert/connection/PG_Connection.rb +35 -0
  25. data/lib/upsert/connection/SQLite3_Database.rb +28 -0
  26. data/lib/upsert/connection/jdbc.rb +105 -0
  27. data/lib/upsert/connection/postgresql.rb +24 -0
  28. data/lib/upsert/connection/sqlite3.rb +19 -0
  29. data/lib/upsert/merge_function.rb +73 -0
  30. data/lib/upsert/merge_function/Java_ComMysqlJdbc_JDBC4Connection.rb +42 -0
  31. data/lib/upsert/merge_function/Java_OrgPostgresqlJdbc_PgConnection.rb +27 -0
  32. data/lib/upsert/merge_function/Java_OrgSqlite_Conn.rb +10 -0
  33. data/lib/upsert/merge_function/Mysql2_Client.rb +36 -0
  34. data/lib/upsert/merge_function/PG_Connection.rb +26 -0
  35. data/lib/upsert/merge_function/SQLite3_Database.rb +10 -0
  36. data/lib/upsert/merge_function/mysql.rb +66 -0
  37. data/lib/upsert/merge_function/postgresql.rb +365 -0
  38. data/lib/upsert/merge_function/sqlite3.rb +43 -0
  39. data/lib/upsert/row.rb +59 -0
  40. data/lib/upsert/version.rb +3 -0
  41. data/spec/active_record_upsert_spec.rb +26 -0
  42. data/spec/binary_spec.rb +21 -0
  43. data/spec/correctness_spec.rb +190 -0
  44. data/spec/database_functions_spec.rb +106 -0
  45. data/spec/database_spec.rb +121 -0
  46. data/spec/hstore_spec.rb +249 -0
  47. data/spec/jruby_spec.rb +9 -0
  48. data/spec/logger_spec.rb +52 -0
  49. data/spec/misc/get_postgres_reserved_words.rb +12 -0
  50. data/spec/misc/mysql_reserved.txt +226 -0
  51. data/spec/misc/pg_reserved.txt +742 -0
  52. data/spec/multibyte_spec.rb +27 -0
  53. data/spec/postgresql_spec.rb +94 -0
  54. data/spec/precision_spec.rb +11 -0
  55. data/spec/reserved_words_spec.rb +50 -0
  56. data/spec/sequel_spec.rb +57 -0
  57. data/spec/spec_helper.rb +417 -0
  58. data/spec/speed_spec.rb +44 -0
  59. data/spec/threaded_spec.rb +57 -0
  60. data/spec/timezones_spec.rb +58 -0
  61. data/spec/type_safety_spec.rb +12 -0
  62. data/travis/install_postgres.sh +18 -0
  63. data/travis/run_docker_db.sh +20 -0
  64. data/travis/tune_mysql.sh +7 -0
  65. data/upsert-java.gemspec +14 -0
  66. data/upsert.gemspec +13 -0
  67. data/upsert.gemspec.common +106 -0
  68. metadata +373 -0
@@ -0,0 +1,66 @@
1
+ class Upsert
2
+ class ColumnDefinition
3
+ # @private
4
+ class Postgresql < ColumnDefinition
5
+ class << self
6
+ # activerecord-3.2.5/lib/active_record/connection_adapters/postgresql_adapter.rb#column_definitions
7
+ def all(connection, quoted_table_name)
8
+ res = connection.execute <<-EOS
9
+ SELECT a.attname AS name, format_type(a.atttypid, a.atttypmod) AS sql_type, d.adsrc AS default
10
+ FROM pg_attribute a LEFT JOIN pg_attrdef d
11
+ ON a.attrelid = d.adrelid AND a.attnum = d.adnum
12
+ WHERE a.attrelid = '#{quoted_table_name}'::regclass
13
+ AND a.attnum > 0 AND NOT a.attisdropped
14
+ EOS
15
+ res.map do |row|
16
+ new connection, row['name'], row['sql_type'], row['default']
17
+ end.sort_by do |cd|
18
+ cd.name
19
+ end
20
+ end
21
+ end
22
+
23
+ # NOTE not using this because it can't be indexed
24
+ # def equality(left, right)
25
+ # "#{left} IS NOT DISTINCT FROM #{right}"
26
+ # end
27
+
28
+ HSTORE_DETECTOR = /hstore/i
29
+
30
+ def initialize(*)
31
+ super
32
+ @hstore_query = !!(sql_type =~ HSTORE_DETECTOR)
33
+ end
34
+
35
+ def hstore?
36
+ @hstore_query
37
+ end
38
+
39
+ def arg_type
40
+ if hstore?
41
+ 'text'
42
+ else
43
+ # JDBC uses prepared statements and properly sends date objects (which are otherwise escaped)
44
+ RUBY_PLATFORM == "java" ? sql_type : super
45
+ end
46
+ end
47
+
48
+ def to_setter_value
49
+ if hstore?
50
+ "#{quoted_setter_name}::hstore"
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ def to_setter
57
+ if hstore?
58
+ # http://stackoverflow.com/questions/9317971/adding-a-key-to-an-empty-hstore-column
59
+ "#{quoted_name} = COALESCE(#{quoted_name}, hstore(array[]::varchar[])) || #{to_setter_value}"
60
+ else
61
+ super
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,34 @@
1
+ class Upsert
2
+ class ColumnDefinition
3
+ # @private
4
+ class Sqlite3 < ColumnDefinition
5
+ class << self
6
+ def all(connection, quoted_table_name)
7
+ # activerecord-3.2.13/lib/active_record/connection_adapters/sqlite_adapter.rb
8
+ connection.execute("PRAGMA table_info(#{quoted_table_name})").map do |row|#, 'SCHEMA').to_hash
9
+ if connection.metal.respond_to?(:results_as_hash) and not connection.metal.results_as_hash
10
+ row = {'name' => row[1], 'type' => row[2], 'dflt_value' => row[4]}
11
+ end
12
+ default = case row["dflt_value"]
13
+ when /^null$/i
14
+ nil
15
+ when /^'(.*)'$/
16
+ $1.gsub(/''/, "'")
17
+ when /^"(.*)"$/
18
+ $1.gsub(/""/, '"')
19
+ else
20
+ row["dflt_value"]
21
+ end
22
+ new connection, row['name'], row['type'], default
23
+ end.sort_by do |cd|
24
+ cd.name
25
+ end
26
+ end
27
+ end
28
+
29
+ def equality(left, right)
30
+ "(#{left} IS #{right} OR (#{left} IS NULL AND #{right} IS NULL))"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,37 @@
1
+ class Upsert
2
+ # @private
3
+ class Connection
4
+ attr_reader :controller
5
+ attr_reader :metal
6
+
7
+ def initialize(controller, metal)
8
+ @controller = controller
9
+ @metal = metal
10
+ end
11
+
12
+ def convert_binary(bind_values)
13
+ bind_values.map do |v|
14
+ case v
15
+ when Upsert::Binary
16
+ binary v
17
+ else
18
+ v
19
+ end
20
+ end
21
+ end
22
+
23
+ def bind_value(v)
24
+ case v
25
+ when Time, DateTime
26
+ Upsert.utc_iso8601 v
27
+ when Date
28
+ v.strftime ISO8601_DATE
29
+ when Symbol
30
+ v.to_s
31
+ else
32
+ v
33
+ end
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ require 'upsert/connection/jdbc'
2
+
3
+ class Upsert
4
+ class Connection
5
+ # @private
6
+ class Java_ComMysqlJdbc_JDBC4Connection < Connection
7
+ include Jdbc
8
+
9
+ def quote_ident(k)
10
+ if metal.useAnsiQuotedIdentifiers
11
+ DOUBLE_QUOTE + k.to_s.gsub(DOUBLE_QUOTE, '""') + DOUBLE_QUOTE
12
+ else
13
+ # Escape backticks by doubling them. Ref http://dev.mysql.com/doc/refman/5.7/en/identifiers.html
14
+ BACKTICK + k.to_s.gsub(BACKTICK, BACKTICK + BACKTICK) + BACKTICK
15
+ end
16
+ end
17
+
18
+ def bind_value(v)
19
+ case v
20
+ when DateTime, Time
21
+ date = v.utc
22
+ java.time.LocalDateTime.of(date.year, date.month, date.day, date.hour, date.min, date.sec, date.nsec)
23
+ when Date
24
+ java.time.LocalDate.of(v.year, v.month, v.day)
25
+ else
26
+ super
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ require_relative "jdbc"
2
+ require_relative "postgresql"
3
+
4
+ class Upsert
5
+ class Connection
6
+ # @private
7
+ class Java_OrgPostgresqlJdbc_PgConnection < Connection
8
+ include Jdbc
9
+ include Postgresql
10
+
11
+ def quote_ident(k)
12
+ DOUBLE_QUOTE + k.to_s.gsub(DOUBLE_QUOTE, '""') + DOUBLE_QUOTE
13
+ end
14
+
15
+ def in_transaction?
16
+ # https://github.com/kares/activerecord-jdbc-adapter/commit/4d6e0e0c52d12b0166810dffc9f898141a23bee6
17
+ ![0, 4].include?(metal.get_transaction_state)
18
+ end
19
+
20
+ def bind_value(v)
21
+ case v
22
+ when DateTime, Time
23
+ date = v.utc
24
+ java.time.LocalDateTime.of(date.year, date.month, date.day, date.hour, date.min, date.sec, date.nsec)
25
+ when Date
26
+ java.time.LocalDate.of(v.year, v.month, v.day)
27
+ else
28
+ super
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,17 @@
1
+ require 'upsert/connection/jdbc'
2
+ require 'upsert/connection/sqlite3'
3
+
4
+ class Upsert
5
+ class Connection
6
+ # @private
7
+ class Java_OrgSqlite_Conn < Connection
8
+ include Jdbc
9
+ include Sqlite3
10
+
11
+ # ?
12
+ def quote_ident(k)
13
+ DOUBLE_QUOTE + k.to_s.gsub(DOUBLE_QUOTE, '""') + DOUBLE_QUOTE
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,76 @@
1
+ class Upsert
2
+ class Connection
3
+ # @private
4
+ class Mysql2_Client < Connection
5
+ def execute(sql)
6
+ Upsert.logger.debug { %{[upsert] #{sql}} }
7
+ if results = metal.query(sql)
8
+ rows = []
9
+ results.each { |row| rows << row }
10
+ if rows[0].is_a? Array
11
+ # you don't know if mysql2 is going to give you an array or a hash... and you shouldn't specify, because it's sticky
12
+ fields = results.fields
13
+ rows.map { |row| Hash[fields.zip(row)] }
14
+ else
15
+ rows
16
+ end
17
+ end
18
+ end
19
+
20
+ def quote_value(v)
21
+ case v
22
+ when NilClass
23
+ NULL_WORD
24
+ when Upsert::Binary
25
+ quote_binary v.value
26
+ when String
27
+ quote_string v
28
+ when TrueClass, FalseClass
29
+ quote_boolean v
30
+ when BigDecimal
31
+ quote_big_decimal v
32
+ when Numeric
33
+ v
34
+ when Symbol
35
+ quote_string v.to_s
36
+ when DateTime, Time
37
+ # mysql doesn't like it when you send timezone to a datetime
38
+ quote_string Upsert.utc_iso8601(v, false)
39
+ when Date
40
+ quote_date v
41
+ else
42
+ raise "not sure how to quote #{v.class}: #{v.inspect}"
43
+ end
44
+ end
45
+
46
+ def quote_boolean(v)
47
+ v ? 'TRUE' : 'FALSE'
48
+ end
49
+
50
+ def quote_string(v)
51
+ SINGLE_QUOTE + metal.escape(v) + SINGLE_QUOTE
52
+ end
53
+
54
+ # This doubles the size of the representation.
55
+ def quote_binary(v)
56
+ X_AND_SINGLE_QUOTE + v.unpack("H*")[0] + SINGLE_QUOTE
57
+ end
58
+
59
+ # put raw binary straight into sql
60
+ # might work if we could get the encoding issues fixed when joining together the values for the sql
61
+ # alias_method :quote_binary, :quote_string
62
+
63
+ def quote_date(v)
64
+ quote_string v.strftime(ISO8601_DATE)
65
+ end
66
+
67
+ def quote_ident(k)
68
+ BACKTICK + metal.escape(k.to_s) + BACKTICK
69
+ end
70
+
71
+ def quote_big_decimal(v)
72
+ v.to_s('F')
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,35 @@
1
+ require_relative "postgresql"
2
+
3
+ class Upsert
4
+ class Connection
5
+ # @private
6
+ class PG_Connection < Connection
7
+ include Postgresql
8
+
9
+ def execute(sql, params = nil)
10
+ if params
11
+ # Upsert.logger.debug { %{[upsert] #{sql} with #{params.inspect}} }
12
+ # The following will blow up if you pass a value that cannot be automatically type-casted,
13
+ # such as passing a string to an integer field. You'll get an error something along the
14
+ # lines of: "invalid input syntax for <type>: <value>"
15
+ metal.exec sql, convert_binary(params)
16
+ else
17
+ Upsert.logger.debug { %{[upsert] #{sql}} }
18
+ metal.exec sql
19
+ end
20
+ end
21
+
22
+ def quote_ident(k)
23
+ metal.quote_ident k.to_s
24
+ end
25
+
26
+ def binary(v)
27
+ { :value => v.value, :format => 1 }
28
+ end
29
+
30
+ def in_transaction?
31
+ ![PG::PQTRANS_IDLE, PG::PQTRANS_UNKNOWN].include?(metal.transaction_status)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ require 'upsert/connection/sqlite3'
2
+
3
+ class Upsert
4
+ class Connection
5
+ # @private
6
+ class SQLite3_Database < Connection
7
+ include Sqlite3
8
+
9
+ def execute(sql, params = nil)
10
+ if params
11
+ Upsert.logger.debug { %{[upsert] #{sql} with #{params.inspect}} }
12
+ metal.execute sql, convert_binary(params)
13
+ else
14
+ Upsert.logger.debug { %{[upsert] #{sql}} }
15
+ metal.execute sql
16
+ end
17
+ end
18
+
19
+ def quote_ident(k)
20
+ DOUBLE_QUOTE + SQLite3::Database.quote(k.to_s) + DOUBLE_QUOTE
21
+ end
22
+
23
+ def binary(v)
24
+ SQLite3::Blob.new v.value
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,105 @@
1
+ class Upsert
2
+ class Connection
3
+ # @private
4
+ module Jdbc
5
+ # /Users/seamusabshere/.rvm/gems/jruby-head/gems/activerecord-jdbc-adapter-1.2.2.1/src/java/arjdbc/jdbc/RubyJdbcConnection.java
6
+ GETTER = {
7
+ java.sql.Types::CHAR => 'getString',
8
+ java.sql.Types::VARCHAR => 'getString',
9
+ java.sql.Types::OTHER => 'getString', # ?! i guess unicode text?
10
+ java.sql.Types::BINARY => 'getBlob',
11
+ java.sql.Types::LONGVARCHAR => 'getString',
12
+ java.sql.Types::BIGINT => 'getLong',
13
+ java.sql.Types::INTEGER => 'getInt',
14
+ java.sql.Types::REAL => "getLong",
15
+ java.sql.Types::ARRAY => ->(r, i){ r.getArray(i).array.to_ary }
16
+ }
17
+ java.sql.Types.constants.each do |type_name|
18
+ i = java.sql.Types.const_get type_name
19
+ unless GETTER.has_key?(i)
20
+ GETTER[i] = 'get' + type_name[0].upcase + type_name[1..-1].downcase
21
+ end
22
+ end
23
+ SETTER = Hash.new do |hash, k|
24
+ hash[k] = 'set' + k
25
+ end.merge(
26
+ 'TrueClass' => 'setBoolean',
27
+ 'FalseClass' => 'setBoolean',
28
+ 'Fixnum' => 'setInt',
29
+ 'Integer' => 'setInt'
30
+ )
31
+
32
+ def binary(v)
33
+ v.value.to_java_bytes.java_object
34
+ end
35
+
36
+ def execute(sql, params = nil)
37
+ has_result = if params
38
+ Upsert.logger.debug { %{[upsert] #{sql} with #{params.inspect}} }
39
+ setters = self.class.const_get(:SETTER)
40
+ statement = metal.prepareStatement sql
41
+ params.each_with_index do |v, i|
42
+ if v.is_a?(Fixnum) && v > 2_147_483_647
43
+ statement.setLong i+1, v
44
+ next
45
+ end
46
+
47
+ case v
48
+ when Upsert::Binary
49
+ statement.setBytes i+1, binary(v)
50
+ when Float, BigDecimal
51
+ statement.setBigDecimal i+1, java.math.BigDecimal.new(v.to_s)
52
+ when NilClass
53
+ # http://stackoverflow.com/questions/4243513/why-does-preparedstatement-setnull-requires-sqltype
54
+ statement.setObject i+1, nil
55
+ when java.time.LocalDateTime, java.time.Instant, java.time.LocalDate
56
+ statement.setObject i+1, v
57
+ else
58
+ setter = setters[v.class.name]
59
+ Upsert.logger.debug { "Setting [#{v.class}, #{v}] via #{setter}" }
60
+ statement.send setter, i+1, v
61
+ end
62
+ end
63
+ statement.execute
64
+ else
65
+ Upsert.logger.debug { %{[upsert] #{sql}} }
66
+ statement = metal.createStatement
67
+ statement.execute sql
68
+ end
69
+ if not has_result
70
+ statement.close
71
+ return
72
+ end
73
+ getters = self.class.const_get(:GETTER)
74
+ raw_result = statement.getResultSet
75
+ meta = raw_result.getMetaData
76
+ count = meta.getColumnCount
77
+ column_name_and_getter = (1..count).inject({}) do |memo, i|
78
+ memo[i] = [ meta.getColumnName(i), getters[meta.getColumnType(i)] ]
79
+ memo
80
+ end
81
+ result = []
82
+ while raw_result.next
83
+ row = {}
84
+ column_name_and_getter.each do |i, cg|
85
+ column_name, getter = cg
86
+ if getter == 'getNull'
87
+ row[column_name] = nil
88
+ elsif getter.respond_to?(:call)
89
+ row[column_name] = getter.call(raw_result, i)
90
+ else
91
+ row[column_name] = raw_result.send(getter, i)
92
+ end
93
+ end
94
+ result << row
95
+ end
96
+ statement.close
97
+ result
98
+ end
99
+
100
+ def in_transaction?
101
+ raise "Not implemented"
102
+ end
103
+ end
104
+ end
105
+ end