baza 0.0.20 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +84 -0
  3. data/.rubocop_todo.yml +17 -135
  4. data/.travis.yml +21 -0
  5. data/Gemfile +10 -7
  6. data/Gemfile.lock +39 -44
  7. data/README.md +61 -3
  8. data/VERSION +1 -1
  9. data/baza.gemspec +146 -98
  10. data/config/best_project_practice_rubocop.yml +8 -0
  11. data/config/best_project_practice_rubocop_todo.yml +6 -0
  12. data/lib/baza.rb +8 -12
  13. data/lib/baza/base_sql_driver.rb +198 -52
  14. data/lib/baza/cloner.rb +1 -0
  15. data/lib/baza/column.rb +26 -0
  16. data/lib/baza/database.rb +19 -3
  17. data/lib/baza/db.rb +69 -271
  18. data/lib/baza/driver.rb +1 -6
  19. data/lib/baza/{drivers → driver}/active_record.rb +65 -21
  20. data/lib/baza/{drivers → driver}/active_record/columns.rb +0 -0
  21. data/lib/baza/driver/active_record/commands.rb +10 -0
  22. data/lib/baza/driver/active_record/databases.rb +10 -0
  23. data/lib/baza/{drivers → driver}/active_record/indexes.rb +0 -0
  24. data/lib/baza/{drivers → driver}/active_record/result.rb +3 -1
  25. data/lib/baza/{drivers → driver}/active_record/tables.rb +0 -0
  26. data/lib/baza/driver/active_record/users.rb +12 -0
  27. data/lib/baza/{drivers → driver}/mysql.rb +9 -26
  28. data/lib/baza/{drivers → driver}/mysql/column.rb +14 -35
  29. data/lib/baza/{drivers → driver}/mysql/columns.rb +9 -12
  30. data/lib/baza/driver/mysql/commands.rb +39 -0
  31. data/lib/baza/driver/mysql/database.rb +64 -0
  32. data/lib/baza/driver/mysql/databases.rb +63 -0
  33. data/lib/baza/{drivers → driver}/mysql/index.rb +0 -0
  34. data/lib/baza/{drivers → driver}/mysql/indexes.rb +0 -0
  35. data/lib/baza/{drivers → driver}/mysql/result.rb +15 -7
  36. data/lib/baza/{drivers → driver}/mysql/sqlspecs.rb +0 -0
  37. data/lib/baza/{drivers → driver}/mysql/table.rb +27 -43
  38. data/lib/baza/{drivers → driver}/mysql/tables.rb +5 -34
  39. data/lib/baza/{drivers → driver}/mysql/unbuffered_result.rb +8 -2
  40. data/lib/baza/driver/mysql/user.rb +22 -0
  41. data/lib/baza/driver/mysql/users.rb +39 -0
  42. data/lib/baza/{drivers → driver}/mysql2.rb +19 -49
  43. data/lib/baza/{drivers → driver}/mysql2/column.rb +0 -0
  44. data/lib/baza/{drivers → driver}/mysql2/columns.rb +0 -0
  45. data/lib/baza/driver/mysql2/commands.rb +2 -0
  46. data/lib/baza/{drivers → driver}/mysql2/database.rb +0 -0
  47. data/lib/baza/{drivers → driver}/mysql2/databases.rb +0 -0
  48. data/lib/baza/{drivers → driver}/mysql2/index.rb +0 -0
  49. data/lib/baza/{drivers → driver}/mysql2/indexes.rb +0 -0
  50. data/lib/baza/{drivers → driver}/mysql2/result.rb +3 -1
  51. data/lib/baza/{drivers → driver}/mysql2/table.rb +0 -0
  52. data/lib/baza/{drivers → driver}/mysql2/tables.rb +0 -0
  53. data/lib/baza/driver/mysql2/user.rb +2 -0
  54. data/lib/baza/driver/mysql2/users.rb +2 -0
  55. data/lib/baza/{drivers → driver}/mysql_java.rb +60 -38
  56. data/lib/baza/{drivers → driver}/mysql_java/column.rb +0 -0
  57. data/lib/baza/{drivers → driver}/mysql_java/columns.rb +0 -0
  58. data/lib/baza/driver/mysql_java/commands.rb +2 -0
  59. data/lib/baza/driver/mysql_java/database.rb +2 -0
  60. data/lib/baza/driver/mysql_java/databases.rb +2 -0
  61. data/lib/baza/{drivers → driver}/mysql_java/index.rb +0 -0
  62. data/lib/baza/{drivers → driver}/mysql_java/indexes.rb +0 -0
  63. data/lib/baza/{drivers → driver}/mysql_java/table.rb +0 -0
  64. data/lib/baza/{drivers → driver}/mysql_java/tables.rb +0 -0
  65. data/lib/baza/driver/mysql_java/user.rb +2 -0
  66. data/lib/baza/driver/mysql_java/users.rb +2 -0
  67. data/lib/baza/driver/pg.rb +80 -0
  68. data/lib/baza/driver/pg/column.rb +125 -0
  69. data/lib/baza/driver/pg/columns.rb +37 -0
  70. data/lib/baza/driver/pg/commands.rb +35 -0
  71. data/lib/baza/driver/pg/create_index_sql_creator.rb +51 -0
  72. data/lib/baza/driver/pg/database.rb +89 -0
  73. data/lib/baza/driver/pg/databases.rb +79 -0
  74. data/lib/baza/driver/pg/index.rb +35 -0
  75. data/lib/baza/driver/pg/indexes.rb +5 -0
  76. data/lib/baza/driver/pg/result.rb +139 -0
  77. data/lib/baza/driver/pg/table.rb +184 -0
  78. data/lib/baza/driver/pg/tables.rb +45 -0
  79. data/lib/baza/{drivers → driver}/sqlite3.rb +6 -24
  80. data/lib/baza/{drivers → driver}/sqlite3/column.rb +22 -24
  81. data/lib/baza/{drivers → driver}/sqlite3/columns.rb +6 -6
  82. data/lib/baza/driver/sqlite3/commands.rb +28 -0
  83. data/lib/baza/{drivers → driver}/sqlite3/database.rb +0 -0
  84. data/lib/baza/{drivers → driver}/sqlite3/databases.rb +0 -1
  85. data/lib/baza/{drivers → driver}/sqlite3/index.rb +0 -0
  86. data/lib/baza/{drivers → driver}/sqlite3/indexes.rb +0 -0
  87. data/lib/baza/{drivers → driver}/sqlite3/result.rb +14 -6
  88. data/lib/baza/{drivers → driver}/sqlite3/sqlspecs.rb +0 -0
  89. data/lib/baza/{drivers → driver}/sqlite3/table.rb +25 -16
  90. data/lib/baza/{drivers → driver}/sqlite3/tables.rb +5 -6
  91. data/lib/baza/{drivers → driver}/sqlite3/unbuffered_result.rb +8 -2
  92. data/lib/baza/{drivers → driver}/sqlite3_java.rb +13 -23
  93. data/lib/baza/{drivers → driver}/sqlite3_java/column.rb +0 -0
  94. data/lib/baza/{drivers → driver}/sqlite3_java/columns.rb +0 -0
  95. data/lib/baza/driver/sqlite3_java/commands.rb +2 -0
  96. data/lib/baza/{drivers → driver}/sqlite3_java/database.rb +0 -0
  97. data/lib/baza/{drivers → driver}/sqlite3_java/index.rb +0 -0
  98. data/lib/baza/{drivers → driver}/sqlite3_java/indexes.rb +0 -0
  99. data/lib/baza/{drivers → driver}/sqlite3_java/table.rb +0 -0
  100. data/lib/baza/{drivers → driver}/sqlite3_java/tables.rb +0 -0
  101. data/lib/baza/{drivers → driver}/sqlite3_java/unbuffered_result.rb +14 -9
  102. data/lib/baza/{drivers → driver}/sqlite3_rhodes.rb +6 -24
  103. data/lib/baza/errors.rb +2 -0
  104. data/lib/baza/idquery.rb +15 -8
  105. data/lib/baza/index.rb +7 -0
  106. data/lib/baza/jdbc_driver.rb +4 -16
  107. data/lib/baza/jdbc_result.rb +20 -12
  108. data/lib/baza/mysql_base_driver.rb +7 -7
  109. data/lib/baza/query_buffer.rb +20 -19
  110. data/lib/baza/row.rb +16 -16
  111. data/lib/baza/sql_queries.rb +3 -0
  112. data/lib/baza/sql_queries/generic_insert.rb +81 -0
  113. data/lib/baza/sql_queries/generic_update.rb +31 -0
  114. data/lib/baza/sql_queries/mysql_upsert.rb +52 -0
  115. data/lib/baza/sql_queries/mysql_upsert_duplicate_key.rb +57 -0
  116. data/lib/baza/sql_queries/non_atomic_upsert.rb +25 -0
  117. data/lib/baza/sql_queries/postgres_upsert_duplicate_key.rb +118 -0
  118. data/lib/baza/sql_queries/select.rb +170 -0
  119. data/lib/baza/sql_queries/sqlite_upsert_duplicate_key.rb +99 -0
  120. data/lib/baza/table.rb +35 -8
  121. data/spec/active_record/models/user.rb +3 -0
  122. data/spec/{cloner_spec.rb → baza/cloner_spec.rb} +0 -0
  123. data/spec/drivers/active_record_mysql2_spec.rb +5 -3
  124. data/spec/drivers/active_record_mysql_spec.rb +2 -1
  125. data/spec/drivers/active_record_pg_spec.rb +20 -0
  126. data/spec/drivers/active_record_sqlite3_spec.rb +2 -1
  127. data/spec/drivers/mysql2_spec.rb +1 -1
  128. data/spec/drivers/mysql_spec.rb +10 -10
  129. data/spec/drivers/pg_spec.rb +18 -0
  130. data/spec/drivers/sqlite3_spec.rb +7 -8
  131. data/spec/info_active_record_example.rb +1 -1
  132. data/spec/{info_active_record_mysql2.rb → info_active_record_mysql2_example.rb} +3 -2
  133. data/spec/info_active_record_mysql2_travis.rb +35 -0
  134. data/spec/{info_active_record_mysql.rb → info_active_record_mysql_example.rb} +5 -4
  135. data/spec/info_active_record_mysql_travis.rb +36 -0
  136. data/spec/info_active_record_pg_example.rb +36 -0
  137. data/spec/info_active_record_pg_travis.rb +34 -0
  138. data/spec/info_active_record_sqlite3.rb +1 -1
  139. data/spec/info_mysql2_example.rb +1 -3
  140. data/spec/{info_mysql2_shippable.rb → info_mysql2_travis.rb} +2 -4
  141. data/spec/info_mysql_example.rb +1 -3
  142. data/spec/{info_mysql_shippable.rb → info_mysql_travis.rb} +2 -4
  143. data/spec/info_pg_example.rb +22 -0
  144. data/spec/info_pg_travis.rb +20 -0
  145. data/spec/info_sqlite3.rb +1 -3
  146. data/spec/spec_helper.rb +1 -1
  147. data/spec/support/driver_active_record_collection.rb +62 -0
  148. data/spec/support/driver_collection.rb +136 -121
  149. data/spec/support/driver_columns_collection.rb +19 -10
  150. data/spec/support/driver_databases_collection.rb +23 -1
  151. data/spec/support/driver_indexes_collection.rb +2 -2
  152. data/spec/support/driver_tables_collection.rb +24 -4
  153. data/spec/support/driver_users_collection.rb +53 -0
  154. metadata +185 -104
  155. data/lib/baza/drivers/mysql/database.rb +0 -28
  156. data/lib/baza/drivers/mysql/databases.rb +0 -35
  157. data/lib/baza/drivers/mysql_java/database.rb +0 -2
  158. data/lib/baza/drivers/mysql_java/databases.rb +0 -2
  159. data/lib/baza/model.rb +0 -875
  160. data/lib/baza/model_custom.rb +0 -155
  161. data/lib/baza/model_handler.rb +0 -910
  162. data/lib/baza/model_handler_sqlhelper.rb +0 -484
  163. data/lib/baza/revision.rb +0 -383
  164. data/shippable.yml +0 -17
  165. data/spec/info_active_record_mysql2_shippable.rb +0 -34
  166. data/spec/info_active_record_mysql_shippable.rb +0 -34
  167. data/spec/model_handler_spec.rb +0 -431
@@ -13,24 +13,24 @@ class Baza::Row
13
13
  @args[key.to_sym] = value
14
14
  end
15
15
 
16
- @args[:objects] = $objects if !@args[:objects] && $objects && $objects.is_a?(Baza::ModelHandler)
17
16
  @args[:col_id] ||= :id
18
17
  raise "No table given." unless @args[:table]
19
18
 
20
19
  if @args[:data] && (@args[:data].is_a?(Integer) || @args[:data].is_a?(Fixnum) || @args[:data].is_a?(String))
21
20
  @data = {@args[:col_id].to_sym => @args[:data].to_s}
22
21
  reload
23
- elsif @args[:data] && @args[:data].is_a?(Hash)
22
+ elsif @args[:data] && @args.fetch(:data).is_a?(Hash)
24
23
  @data = {}
25
- @args[:data].each do |key, value|
26
- @data[key.to_sym] = value
24
+ @args.fetch(:data).each do |key, value|
25
+ key = key.to_sym unless key.is_a?(Fixnum)
26
+ @data[key] = value
27
27
  end
28
28
  elsif @args[:id]
29
29
  @data = {}
30
30
  @data[@args[:col_id].to_sym] = @args[:id]
31
31
  reload
32
32
  else
33
- raise ArgumentError.new("Invalid data: #{@args[:data]} (#{@args[:data].class})")
33
+ raise ArgumentError, "Invalid data: #{@args[:data]} (#{@args[:data].class})"
34
34
  end
35
35
  end
36
36
 
@@ -43,7 +43,7 @@ class Baza::Row
43
43
  false
44
44
  end
45
45
 
46
- alias_method :objects, :ob
46
+ alias objects ob
47
47
 
48
48
  def reload
49
49
  last_id = id
@@ -59,14 +59,14 @@ class Baza::Row
59
59
  end
60
60
 
61
61
  def update(newdata)
62
- db.update(@args[:table], newdata, @args[:col_id] => id)
62
+ db.update(@args.fetch(:table), newdata, @args.fetch(:col_id) => id)
63
63
  reload
64
64
 
65
65
  ob.call("object" => self, "signal" => "update") if ob
66
66
  end
67
67
 
68
68
  def delete
69
- db.delete(@args[:table], @args[:col_id] => id)
69
+ db.delete(@args.fetch(:table), @args.fetch(:col_id) => id)
70
70
  destroy
71
71
  end
72
72
 
@@ -100,7 +100,7 @@ class Baza::Row
100
100
  end
101
101
 
102
102
  def id
103
- @data[@args[:col_id]]
103
+ @data.fetch(@args.fetch(:col_id))
104
104
  end
105
105
 
106
106
  def to_param
@@ -108,18 +108,18 @@ class Baza::Row
108
108
  end
109
109
 
110
110
  def title
111
- return @data[@args[:col_title].to_sym] if @args[:col_title]
111
+ return @data[@args.fetch(:col_title).to_sym] if @args[:col_title]
112
112
 
113
113
  if @data.key?(:title)
114
- return @data[:title]
114
+ return @data.fetch(:title)
115
115
  elsif @data.key?(:name)
116
- return @data[:name]
116
+ return @data.fetch(:name)
117
117
  end
118
118
 
119
119
  raise "'col_title' has not been set for the class: '#{self.class}'."
120
120
  end
121
121
 
122
- alias_method :name, :title
122
+ alias name title
123
123
 
124
124
  def each(*args, &blk)
125
125
  @data.each(*args, &blk)
@@ -138,10 +138,10 @@ class Baza::Row
138
138
  end
139
139
 
140
140
  def method_missing(func_name, *args)
141
- if match = func_name.to_s.match(/^(\S+)\?$/) && @data.key?(match[1].to_sym)
142
- if @data[match[1].to_sym] == "1" || @data[match[1].to_sym] == "yes"
141
+ if (match = func_name.to_s.match(/^(\S+)\?$/)) && @data.key?(match[1].to_sym)
142
+ if @data.fetch(match[1].to_sym) == "1" || @data.fetch(match[1].to_sym) == "yes"
143
143
  return true
144
- elsif @data[match[1].to_sym] == "0" || @data[match[1].to_sym] == "no"
144
+ elsif @data.fetch(match[1].to_sym) == "0" || @data.fetch(match[1].to_sym) == "no"
145
145
  return false
146
146
  end
147
147
  end
@@ -0,0 +1,3 @@
1
+ class Baza::SqlQueries
2
+ AutoAutoloader.autoload_sub_classes(self, __FILE__)
3
+ end
@@ -0,0 +1,81 @@
1
+ class Baza::SqlQueries::GenericInsert
2
+ def initialize(args)
3
+ @db = args.fetch(:db)
4
+ @table_name = args.fetch(:table_name)
5
+ @data = args.fetch(:data)
6
+ @buffer = args[:buffer]
7
+ @return_sql = args[:return_sql]
8
+ @return_id = args[:return_id]
9
+ end
10
+
11
+ def execute
12
+ if @return_sql
13
+ to_sql
14
+ elsif @buffer
15
+ @buffer.query(to_sql)
16
+ else
17
+ @db.query(to_sql)
18
+ @db.last_id if @return_id
19
+ end
20
+ end
21
+
22
+ def to_sql
23
+ sql = "INSERT INTO #{@db.sep_table}#{@db.escape_table(@table_name)}#{@db.sep_table}"
24
+
25
+ if !@data || @data.empty?
26
+ sql << " #{sql_default_values}"
27
+ else
28
+ sql << " #{sql_columns} VALUES #{sql_values}"
29
+ end
30
+
31
+ sql
32
+ end
33
+
34
+ private
35
+
36
+ def sql_default_values
37
+ if @db.opts.fetch(:type).to_s.include?("mysql")
38
+ "VALUES ()" # This is the correct syntax for inserting a blank row in MySQL.
39
+ elsif @db.opts.fetch(:type).to_s.include?("sqlite3")
40
+ "DEFAULT VALUES"
41
+ else
42
+ raise "Unknown database-type: '#{@db.opts.fetch(:type)}'."
43
+ end
44
+ end
45
+
46
+ def sql_columns
47
+ sql = "("
48
+
49
+ first = true
50
+ @data.each_key do |key|
51
+ if first
52
+ first = false
53
+ else
54
+ sql << ", "
55
+ end
56
+
57
+ sql << "#{@db.sep_col}#{@db.escape_column(key)}#{@db.sep_col}"
58
+ end
59
+
60
+ sql << ")"
61
+ sql
62
+ end
63
+
64
+ def sql_values
65
+ sql = "("
66
+
67
+ first = true
68
+ @data.each_value do |value|
69
+ if first
70
+ first = false
71
+ else
72
+ sql << ", "
73
+ end
74
+
75
+ sql << @db.sqlval(value)
76
+ end
77
+
78
+ sql << ")"
79
+ sql
80
+ end
81
+ end
@@ -0,0 +1,31 @@
1
+ class Baza::SqlQueries::GenericUpdate
2
+ def initialize(args)
3
+ @db = args.fetch(:db)
4
+ @table_name = args.fetch(:table_name)
5
+ @data = args.fetch(:data)
6
+ @terms = args.fetch(:terms)
7
+ @buffer = args[:buffer]
8
+ end
9
+
10
+ def execute
11
+ if @buffer
12
+ @buffer.query(to_sql)
13
+ else
14
+ @db.query(to_sql)
15
+ end
16
+ end
17
+
18
+ def to_sql
19
+ sql = "UPDATE #{@db.sep_col}#{@db.escape_table(@table_name)}#{@db.sep_col} SET "
20
+
21
+ first = true
22
+ @data.each do |key, value|
23
+ sql << ", " unless first
24
+ first = false if first
25
+ sql << "#{@db.sep_col}#{@db.escape_column(key)}#{@db.sep_col} = #{@db.sqlval(value)}"
26
+ end
27
+
28
+ sql << " WHERE #{@db.sql_make_where(@terms)}" if @terms && !@terms.empty?
29
+ sql
30
+ end
31
+ end
@@ -0,0 +1,52 @@
1
+ class Baza::SqlQueries::MysqlUpsert
2
+ def initialize(args)
3
+ @db = args.fetch(:db)
4
+ @table_name = args.fetch(:table_name)
5
+ @updates = args.fetch(:updates)
6
+ @terms = args.fetch(:terms)
7
+ end
8
+
9
+ def execute
10
+ procedure_name = "baza_upsert_#{SecureRandom.hex(5)}"
11
+
12
+ sql = "CREATE PROCEDURE `#{@db.escape_table(procedure_name)}` () BEGIN\n"
13
+ sql << "\tIF EXISTS(#{select_query}) THEN\n"
14
+ sql << "\t\t#{update_sql};\n"
15
+ sql << "\tELSE\n"
16
+ sql << "\t\t#{insert_sql};\n"
17
+ sql << "\tEND IF;\n"
18
+ sql << "END;\n"
19
+
20
+ @db.query(sql)
21
+
22
+ begin
23
+ @db.query("CALL `#{@db.escape_table(procedure_name)}`")
24
+ ensure
25
+ @db.query("DROP PROCEDURE `#{@db.escape_table(procedure_name)}`")
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def select_query
32
+ sql = ""
33
+ sql << "SELECT * FROM #{@db.sep_table}#{@db.escape_table(@table_name)}#{@db.sep_table} WHERE"
34
+
35
+ first = true
36
+ @terms.each do |column, value|
37
+ sql << " AND" unless first
38
+ first = false if first
39
+ sql << " #{@db.sep_col}#{@db.escape_column(column)}#{@db.sep_col} = #{@db.sqlval(value)}"
40
+ end
41
+
42
+ sql
43
+ end
44
+
45
+ def update_sql
46
+ @db.update(@table_name, @updates, @terms, return_sql: true)
47
+ end
48
+
49
+ def insert_sql
50
+ @db.insert(@table_name, @updates.merge(@terms), return_sql: true)
51
+ end
52
+ end
@@ -0,0 +1,57 @@
1
+ class Baza::SqlQueries::MysqlUpsertDuplicateKey
2
+ def initialize(args)
3
+ @db = args.fetch(:db)
4
+ @table_name = args.fetch(:table_name)
5
+ @updates = StringCases.stringify_keys(args.fetch(:updates))
6
+ @terms = StringCases.stringify_keys(args.fetch(:terms))
7
+ @buffer = args[:buffer]
8
+ @return_id = args[:return_id]
9
+ end
10
+
11
+ def to_sql
12
+ sql = insert_sql
13
+ sql << " ON DUPLICATE KEY UPDATE"
14
+
15
+ first = true
16
+
17
+ if @return_id
18
+ sql << " #{@db.sep_col}#{@db.escape_column(primary_key_column_name)}#{@db.sep_col} = LAST_INSERT_ID(#{@db.sep_col}#{@db.escape_column(primary_key_column_name)}#{@db.sep_col})"
19
+ first = false
20
+ end
21
+
22
+ @updates.each do |key, value|
23
+ sql << "," unless first
24
+ first = false if first
25
+ sql << " #{@db.sep_col}#{@db.escape_column(key)}#{@db.sep_col} = #{@db.sqlval(value)}"
26
+ end
27
+
28
+ sql
29
+ end
30
+
31
+ def execute
32
+ if @buffer
33
+ @buffer.query(to_sql)
34
+ else
35
+ @db.query(to_sql)
36
+ return @db.query(last_insert_sql).fetch.fetch(:id).to_i if @return_id
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def insert_sql
43
+ @db.insert(@table_name, @updates.merge(@terms), return_sql: true)
44
+ end
45
+
46
+ def table
47
+ @table ||= @db.tables[@table_name.to_s]
48
+ end
49
+
50
+ def primary_key_column_name
51
+ @primary_key_column_name ||= table.columns(primarykey: true).first.name
52
+ end
53
+
54
+ def last_insert_sql
55
+ "SELECT LAST_INSERT_ID() AS `id` FROM #{@db.sep_table}#{@db.escape_table(@table_name)}#{@db.sep_table}"
56
+ end
57
+ end
@@ -0,0 +1,25 @@
1
+ class Baza::SqlQueries::NonAtomicUpsert
2
+ def initialize(args)
3
+ @db = args.fetch(:db)
4
+ @table_name = args.fetch(:table_name)
5
+ @updates = args.fetch(:updates)
6
+ @terms = args.fetch(:terms)
7
+ @buffer = args[:buffer]
8
+ end
9
+
10
+ def execute
11
+ row = @db.single(@table_name, @terms)
12
+
13
+ if @buffer
14
+ obj = @buffer
15
+ else
16
+ obj = @db
17
+ end
18
+
19
+ if row
20
+ obj.update(@table_name, @updates, @terms)
21
+ else
22
+ obj.insert(@table_name, @terms.merge(@updates))
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,118 @@
1
+ class Baza::SqlQueries::PostgresUpsertDuplicateKey
2
+ def initialize(args)
3
+ @db = args.fetch(:db)
4
+ @table_name = args.fetch(:table_name)
5
+ @updates = StringCases.stringify_keys(args.fetch(:updates))
6
+ @terms = StringCases.stringify_keys(args.fetch(:terms))
7
+ @return_id = args[:return_id]
8
+ end
9
+
10
+ def execute
11
+ if @db.commands.version.to_f >= 9.5
12
+ @db.query(on_conflict_sql)
13
+ elsif @terms.empty?
14
+ return insert_and_register_conflict
15
+ else
16
+ @db.query(begin_update_exception_sql)
17
+ end
18
+
19
+ @db.last_id if @return_id
20
+ end
21
+
22
+ private
23
+
24
+ def insert_and_register_conflict
25
+ @db.query(insert_sql)
26
+ @db.last_id if @return_id
27
+ rescue => e
28
+ if (match = e.message.match(/Key \((.+)\)=\((.+)\) already exists/))
29
+ column_name = match[1]
30
+ conflicting_value = match[2]
31
+
32
+ @terms = {column_name => conflicting_value}
33
+ @db.query(begin_update_exception_sql)
34
+
35
+ if @return_id
36
+ primary_column = table.columns.find(&:primarykey?).name.to_sym
37
+ data = @db.single(@table_name, column_name => conflicting_value)
38
+ return data.fetch(primary_column).to_i
39
+ end
40
+ else
41
+ raise e
42
+ end
43
+ end
44
+
45
+ def begin_update_exception_sql
46
+ sql = "do $$\n"
47
+ sql << "BEGIN\n"
48
+ sql << "\t#{insert_sql};\n"
49
+ sql << "EXCEPTION WHEN unique_violation THEN\n"
50
+ sql << "\t#{update_sql};\n"
51
+ sql << "END $$;"
52
+
53
+ sql
54
+ end
55
+
56
+ def on_conflict_sql
57
+ "#{insert_sql} ON CONFLICT DO UPDATE #{update_set_sql}"
58
+ end
59
+
60
+ def insert_sql
61
+ sql = "INSERT INTO #{@db.sep_table}#{@db.escape_table(@table_name)}#{@db.sep_table} ("
62
+
63
+ combined_data = @updates.merge(@terms)
64
+
65
+ first = true
66
+ combined_data.each_key do |column_name|
67
+ sql << ", " unless first
68
+ first = false if first
69
+ sql << "#{@db.sep_col}#{@db.escape_column(column_name)}#{@db.sep_col}"
70
+ end
71
+
72
+ sql << ") VALUES ("
73
+
74
+ first = true
75
+ combined_data.each_value do |value|
76
+ sql << ", " unless first
77
+ first = false if first
78
+ sql << @db.sqlval(value).to_s
79
+ end
80
+
81
+ sql << ")"
82
+ sql
83
+ end
84
+
85
+ def update_sql
86
+ "UPDATE #{@db.sep_table}#{@db.escape_table(@table_name)}#{@db.sep_table} #{update_set_sql} #{update_where_sql}"
87
+ end
88
+
89
+ def update_set_sql
90
+ sql = "SET "
91
+
92
+ first = true
93
+ @updates.each do |key, value|
94
+ sql << ", " unless first
95
+ first = false if first
96
+ sql << "#{@db.sep_col}#{@db.escape_column(key)}#{@db.sep_col} = #{@db.sqlval(value)}"
97
+ end
98
+
99
+ sql
100
+ end
101
+
102
+ def update_where_sql
103
+ sql = "WHERE "
104
+
105
+ first = true
106
+ @terms.each do |key, value|
107
+ sql << " AND " unless first
108
+ first = false if first
109
+ sql << "#{@db.sep_col}#{@db.escape_column(key)}#{@db.sep_col} = #{@db.sqlval(value)}"
110
+ end
111
+
112
+ sql
113
+ end
114
+
115
+ def table
116
+ @table ||= @db.tables[@table_name.to_s]
117
+ end
118
+ end