@@ -19,6 +19,7 @@ package org.apache.spark.sql.jdbc
1919
2020import java .sql .Connection
2121import java .util .Properties
22+ import org .apache .spark .sql .SaveMode
2223
2324import org .apache .spark .sql .Column
2425import org .apache .spark .sql .catalyst .expressions .Literal
@@ -27,8 +28,9 @@ import org.apache.spark.tags.DockerTest
2728
2829@ DockerTest
2930class PostgresIntegrationSuite extends DockerJDBCIntegrationSuite {
31+ import testImplicits ._
3032 override val db = new DatabaseOnDocker {
31- override val imageName = " postgres:9.4.5 "
33+ override val imageName = " postgres:9.5.4 "
3234 override val env = Map (
3335 " POSTGRES_PASSWORD" -> " rootpass"
3436 )
@@ -55,6 +57,26 @@ class PostgresIntegrationSuite extends DockerJDBCIntegrationSuite {
5557 + " null, null, null, null, null, "
5658 + " null, null, null, null, null, null, null)"
5759 ).executeUpdate()
60+ conn.prepareStatement(" CREATE TABLE upsertT0 " +
61+ " (c1 INTEGER, c2 INTEGER, c3 INTEGER, primary key(c1))" ).executeUpdate()
62+ conn.prepareStatement(" INSERT INTO upsertT0 VALUES (1, 2, 3), (2, 3, 4), (3, 4, 5)" )
63+ .executeUpdate()
64+ conn.prepareStatement(" CREATE TABLE upsertT1 " +
65+ " (c1 INTEGER, c2 INTEGER, c3 INTEGER, primary key(c1))" ).executeUpdate()
66+ conn.prepareStatement(" INSERT INTO upsertT1 VALUES (1, 2, 3), (2, 3, 4), (3, 4, 5)" )
67+ .executeUpdate()
68+ conn.prepareStatement(" CREATE TABLE upsertT2 " +
69+ " (c1 INTEGER, c2 INTEGER, c3 INTEGER, primary key(c1, c2))" ).executeUpdate()
70+ conn.prepareStatement(" INSERT INTO upsertT2 VALUES (1, 2, 3), (2, 3, 4), (3, 4, 5)" )
71+ .executeUpdate()
72+ conn.prepareStatement(" CREATE TABLE upsertT3 " +
73+ " (c1 INTEGER, c2 INTEGER, c3 INTEGER, primary key(c1))" ).executeUpdate()
74+ conn.prepareStatement(" INSERT INTO upsertT3 VALUES (1, 2, 3), (2, 3, 4), (3, 4, 5)" )
75+ .executeUpdate()
76+ conn.prepareStatement(" CREATE TABLE upsertT4 " +
77+ " (c1 INTEGER, c2 INTEGER, c3 INTEGER, primary key(c1))" ).executeUpdate()
78+ conn.prepareStatement(" INSERT INTO upsertT4 VALUES (1, 2, 3), (2, 3, 4), (3, 4, 5)" )
79+ .executeUpdate()
5880 }
5981
6082 test(" Type mapping for various types" ) {
@@ -126,4 +148,93 @@ class PostgresIntegrationSuite extends DockerJDBCIntegrationSuite {
126148 assert(schema(0 ).dataType == FloatType )
127149 assert(schema(1 ).dataType == ShortType )
128150 }
151+
152+ test(" Upsert and OverWrite mode" ) {
153+ // existing table has these rows
154+ // (1, 2, 3), (2, 3, 4), (3, 4, 5)
155+ val df1 = spark.read.jdbc(jdbcUrl, " upsertT0" , new Properties ())
156+ assert(df1.filter(" c1=1" ).collect.head.getInt(1 ) == 2 )
157+ assert(df1.filter(" c1=1" ).collect.head.getInt(2 ) == 3 )
158+ assert(df1.filter(" c1=2" ).collect.head.getInt(1 ) == 3 )
159+ assert(df1.filter(" c1=2" ).collect.head.getInt(2 ) == 4 )
160+ val df2 = Seq ((1 , 3 , 6 ), (2 , 5 , 6 )).toDF(" c1" , " c2" , " c3" )
161+ // it will do the Overwrite, not upsert
162+ df2.write.mode(SaveMode .Overwrite )
163+ .option(" upsert" , true ).option(" upsertConditionColumn" , " c1" )
164+ .jdbc(jdbcUrl, " upsertT0" , new Properties )
165+ val df3 = spark.read.jdbc(jdbcUrl, " upsertT0" , new Properties ())
166+ assert(df3.filter(" c1=1" ).collect.head.getInt(1 ) == 3 )
167+ assert(df3.filter(" c1=1" ).collect.head.getInt(2 ) == 6 )
168+ assert(df3.filter(" c1=2" ).collect.head.getInt(1 ) == 5 )
169+ assert(df3.filter(" c1=2" ).collect.head.getInt(2 ) == 6 )
170+ assert(df3.filter(" c1=3" ).collect.size == 0 )
171+ }
172+
173+ test(" upsert with Append and negative option values" ) {
174+ val df1 = Seq ((1 , 3 , 6 ), (2 , 5 , 6 )).toDF(" c1" , " c2" , " c3" )
175+ val m = intercept[java.sql.SQLException ] {
176+ df1.write.mode(SaveMode .Append ).option(" upsert" , true ).option(" upsertConditionColumn" , " C11" )
177+ .jdbc(jdbcUrl, " upsertT1" , new Properties )
178+ }.getMessage
179+ assert(m.contains(" column C11 not found" ))
180+
181+ val n = intercept[java.sql.SQLException ] {
182+ df1.write.mode(SaveMode .Append ).option(" upsert" , true ).option(" upsertConditionColumn" , " C11" )
183+ .jdbc(jdbcUrl, " upsertT1" , new Properties )
184+ }.getMessage
185+ assert(n.contains(" column C11 not found" ))
186+
187+ val o = intercept[org.apache.spark.SparkException ] {
188+ df1.write.mode(SaveMode .Append ).option(" upsert" , true ).option(" upsertconditionColumn" , " c2" )
189+ .jdbc(jdbcUrl, " upsertT1" , new Properties )
190+ }.getMessage
191+ assert(o.contains(" there is no unique or exclusion constraint matching the ON CONFLICT" ))
192+ }
193+
194+ test(" upsert with Append without existing table" ) {
195+ val df1 = Seq ((1 , 3 ), (2 , 5 )).toDF(" c1" , " c2" )
196+ df1.write.mode(SaveMode .Append ).option(" upsert" , true ).option(" upsertConditionColumn" , " c1" )
197+ .jdbc(jdbcUrl, " upsertT" , new Properties )
198+ val df2 = spark.read.jdbc(jdbcUrl, " upsertT" , new Properties )
199+ assert(df2.count() == 2 )
200+ assert(df2.filter(" C1=1" ).collect.head.get(1 ) == 3 )
201+
202+ // table upsertT create without primary key or unique constraints, it will throw the exception
203+ val df3 = Seq ((1 , 4 )).toDF(" c1" , " c2" )
204+ val p = intercept[org.apache.spark.SparkException ] {
205+ df3.write.mode(SaveMode .Append ).option(" upsert" , true ).option(" upsertConditionColumn" , " c1" )
206+ .jdbc(jdbcUrl, " upsertT" , new Properties )
207+ }.getMessage
208+ assert(p.contains(" there is no unique or exclusion constraint matching the ON CONFLICT specification" ))
209+ }
210+
211+ test(" Upsert & Append test -- matching one column" ) {
212+ val df1 = spark.read.jdbc(jdbcUrl, " upsertT3" , new Properties ())
213+ assert(df1.filter(" c1=1" ).collect.head.getInt(1 ) == 2 )
214+ assert(df1.filter(" c1=1" ).collect.head.getInt(2 ) == 3 )
215+ assert(df1.filter(" c1=4" ).collect.size == 0 )
216+ // update Row(1, 2, 3) to (1, 3, 6) and insert new Row(4, 5, 6)
217+ val df2 = Seq ((1 , 3 , 6 ), (4 , 5 , 6 )).toDF(" c1" , " c2" , " c3" )
218+ // condition on one column
219+ df2.write.mode(SaveMode .Append )
220+ .option(" upsert" , true ).option(" upsertConditionColumn" , " c1" ).option(" upsertUpdateColumn" , " c2, c3" )
221+ .jdbc(jdbcUrl, " upsertT3" , new Properties )
222+ val df3 = spark.read.jdbc(jdbcUrl, " upsertT3" , new Properties ())
223+ assert(df3.filter(" c1=1" ).collect.head.getInt(1 ) == 3 )
224+ assert(df3.filter(" c1=1" ).collect.head.getInt(2 ) == 6 )
225+ assert(df3.filter(" c1=4" ).collect.size == 1 )
226+ }
227+
228+ test(" Upsert & Append test -- matching two columns" ) {
229+ val df1 = spark.read.jdbc(jdbcUrl, " upsertT2" , new Properties ())
230+ assert(df1.filter(" c1=1" ).collect.head.getInt(1 ) == 2 )
231+ assert(df1.filter(" c1=1" ).collect.head.getInt(2 ) == 3 )
232+ // update Row(2, 3, 4) to Row(2, 3, 10) that matches 2 columns
233+ val df2 = Seq ((2 , 3 , 10 )).toDF(" c1" , " c2" , " c3" )
234+ df2.write.mode(SaveMode .Append )
235+ .option(" upsert" , true ).option(" upsertConditionColumn" , " c1, c2" )
236+ .jdbc(jdbcUrl, " upsertT2" , new Properties )
237+ val df3 = spark.read.jdbc(jdbcUrl, " upsertT2" , new Properties ())
238+ assert(df3.filter(" c1=2" ).collect.head.getInt(2 ) == 10 )
239+ }
129240}
0 commit comments