View Javadoc
1   package org.dbunit.ext.postgresql;
2   
3   import static org.assertj.core.api.Assertions.assertThat;
4   
5   import java.io.StringReader;
6   import java.sql.Statement;
7   import java.sql.Types;
8   import java.util.Objects;
9   
10  import org.dbunit.DatabaseEnvironment;
11  import org.dbunit.database.DatabaseConfig;
12  import org.dbunit.database.IDatabaseConnection;
13  import org.dbunit.dataset.Column;
14  import org.dbunit.dataset.IDataSet;
15  import org.dbunit.dataset.ITable;
16  import org.dbunit.dataset.ITableMetaData;
17  import org.dbunit.dataset.ReplacementDataSet;
18  import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
19  import org.dbunit.operation.DatabaseOperation;
20  import org.junit.jupiter.api.AfterEach;
21  import org.junit.jupiter.api.BeforeEach;
22  import org.junit.jupiter.api.Test;
23  import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
24  import org.xml.sax.InputSource;
25  
26  import com.fasterxml.jackson.databind.ObjectMapper;
27  
28  /**
29   * Integration test proving {@link JsonType} round-trips PostgreSQL
30   * {@code json}/{@code jsonb} column values, including a null value, through
31   * a real database (issue 574).
32   *
33   * @author Jeff Jensen
34   * @since 3.5.0
35   */
36  @EnabledIfSystemProperty(named = "dbunit.profile", matches = "postgresql")
37  class PostgresqlJsonIT
38  {
39      private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
40  
41      private IDatabaseConnection _connection;
42      private final String testTable = "json_test";
43      // @formatter:off
44      private static final String xmlData = "<?xml version=\"1.0\"?>" +
45              "<dataset>" +
46              "<JSON_TEST ID=\"1\" DATA=\"{&quot;a&quot;:1}\" DATA_B=\"{&quot;b&quot;:2}\" />" +
47              "<JSON_TEST ID=\"2\" DATA=\"[NULL]\" DATA_B=\"[NULL]\" />" +
48              "</dataset>";
49      // @formatter:on
50  
51      @BeforeEach
52      protected void setUp() throws Exception
53      {
54          // Load active postgreSQL profile and connection from Maven pom.xml.
55          _connection = DatabaseEnvironment.getInstance().getConnection();
56          try (Statement stat = _connection.getConnection().createStatement())
57          {
58              stat.execute("DROP TABLE IF EXISTS " + testTable + ";");
59              stat.execute("CREATE TABLE " + testTable
60                      + "(ID INTEGER NOT NULL, DATA json, DATA_B jsonb);");
61          }
62          // Mirrors PostgresqlUuidIT: the table isn't visible to a fresh
63          // dataset without reopening the connection.
64          _connection.close();
65          _connection = DatabaseEnvironment.getInstance().getConnection();
66      }
67  
68      @AfterEach
69      protected void tearDown() throws Exception
70      {
71          if (!Objects.isNull(_connection))
72          {
73              try (Statement stat =
74                      _connection.getConnection().createStatement())
75              {
76                  stat.execute("DROP TABLE IF EXISTS " + testTable + ";");
77              } finally
78              {
79                  _connection.close();
80                  _connection = null;
81              }
82          }
83      }
84  
85      @Test
86      void testJsonDataType_withJsonAndJsonbColumns_roundTripsThroughDatabase()
87              throws Exception
88      {
89          assertThat(_connection).as("didn't get a connection.").isNotNull();
90          final DatabaseConfig config = _connection.getConfig();
91          config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY,
92                  new PostgresqlDataTypeFactory());
93  
94          final ReplacementDataSet dataSet =
95                  new ReplacementDataSet(new FlatXmlDataSetBuilder()
96                          .build(new InputSource(new StringReader(xmlData))));
97          dataSet.addReplacementObject("[NULL]", null);
98          dataSet.setStrictReplacement(true);
99  
100         final IDataSet metaDataSet = _connection.createDataSet();
101         final ITableMetaData itmd = metaDataSet.getTableMetaData(testTable);
102         boolean dataChecked = false;
103         boolean dataBChecked = false;
104         for (final Column col : itmd.getColumns())
105         {
106             if ("DATA".equalsIgnoreCase(col.getColumnName()))
107             {
108                 dataChecked = true;
109                 assertThat(col.getDataType().getSqlType())
110                         .as("DATA column sql type.").isEqualTo(Types.OTHER);
111                 assertThat(col.getSqlTypeName()).as("DATA column sql type name.")
112                         .isEqualTo("json");
113             } else if ("DATA_B".equalsIgnoreCase(col.getColumnName()))
114             {
115                 dataBChecked = true;
116                 assertThat(col.getDataType().getSqlType())
117                         .as("DATA_B column sql type.").isEqualTo(Types.OTHER);
118                 assertThat(col.getSqlTypeName()).as("DATA_B column sql type name.")
119                         .isEqualTo("jsonb");
120             }
121         }
122         assertThat(dataChecked).as("The DATA column should be present in the metadata.")
123                 .isTrue();
124         assertThat(dataBChecked).as("The DATA_B column should be present in the metadata.")
125                 .isTrue();
126 
127         DatabaseOperation.CLEAN_INSERT.execute(_connection, dataSet);
128 
129         final IDataSet actualDataSet = _connection.createDataSet();
130         final ITable actualTable = actualDataSet.getTable(testTable);
131 
132         // PostgreSQL stores "json" verbatim but reformats "jsonb" on write
133         // (e.g. inserted whitespace), so compare parsed document structure
134         // rather than raw text.
135         assertThat(OBJECT_MAPPER.readTree(
136                 String.valueOf(actualTable.getValue(0, "DATA"))))
137                         .as("DATA (json) row 0 should round-trip the same document.")
138                         .isEqualTo(OBJECT_MAPPER.readTree("{\"a\":1}"));
139         assertThat(OBJECT_MAPPER.readTree(
140                 String.valueOf(actualTable.getValue(0, "DATA_B"))))
141                         .as("DATA_B (jsonb) row 0 should round-trip the same document.")
142                         .isEqualTo(OBJECT_MAPPER.readTree("{\"b\":2}"));
143 
144         assertThat(actualTable.getValue(1, "DATA"))
145                 .as("DATA (json) row 1 should be null.").isNull();
146         assertThat(actualTable.getValue(1, "DATA_B"))
147                 .as("DATA_B (jsonb) row 1 should be null.").isNull();
148     }
149 }