DBUpdateLargeObjects.java

  1. /*
  2.  * Copyright 2013 Gregory Graham.
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *      http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  */
  16. package nz.co.gregs.dbvolution.actions;

  17. import java.io.BufferedInputStream;
  18. import java.io.BufferedReader;
  19. import java.io.CharArrayReader;
  20. import java.io.IOError;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.io.InputStreamReader;
  24. import java.nio.charset.Charset;
  25. import java.sql.PreparedStatement;
  26. import java.sql.SQLException;
  27. import java.util.ArrayList;
  28. import java.util.List;
  29. import java.util.logging.Level;
  30. import java.util.logging.Logger;

  31. import nz.co.gregs.dbvolution.databases.DBDatabase;
  32. import nz.co.gregs.dbvolution.DBRow;
  33. import nz.co.gregs.dbvolution.databases.DBStatement;
  34. import nz.co.gregs.dbvolution.databases.QueryIntention;
  35. import nz.co.gregs.dbvolution.databases.definitions.DBDefinition;
  36. import nz.co.gregs.dbvolution.internal.query.LargeObjectHandlerType;
  37. import nz.co.gregs.dbvolution.datatypes.DBLargeObject;
  38. import nz.co.gregs.dbvolution.datatypes.QueryableDatatype;
  39. import nz.co.gregs.dbvolution.exceptions.DBRuntimeException;
  40. import nz.co.gregs.dbvolution.internal.properties.PropertyWrapper;
  41. import org.apache.commons.codec.binary.Base64;
  42. import org.apache.commons.logging.Log;
  43. import org.apache.commons.logging.LogFactory;

  44. /**
  45.  * Provides support for the abstract concept of updating rows with BLOB columns.
  46.  *
  47.  * <p>
  48.  * The best way to use this is by using {@link DBUpdate#getUpdates(nz.co.gregs.dbvolution.DBRow...)
  49.  * } to automatically use this action.
  50.  *
  51.  * <p style="color: #F90;">Support DBvolution at
  52.  * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
  53.  *
  54.  * @author Gregory Graham
  55.  */
  56. public class DBUpdateLargeObjects extends DBUpdate {

  57.     private static final long serialVersionUID = 1l;

  58.     private static final Log LOG = LogFactory.getLog(DBUpdateLargeObjects.class);

  59.     /**
  60.      * Creates a DBUpdateLargeObjects action for the supplied row.
  61.      *
  62.      * @param row the row to be updated
  63.      */
  64.     protected DBUpdateLargeObjects(DBRow row) {
  65.         super(row, QueryIntention.UPDATE_ROW_WITH_LARGE_OBJECT);
  66.     }
  67.     /**
  68.      * Creates a DBUpdateLargeObjects action for the supplied row.
  69.      *
  70.      * @param row the row to be updated
  71.      * @param intent the intended action this is being used for
  72.      */
  73.     protected DBUpdateLargeObjects(DBRow row, QueryIntention intent) {
  74.         super(row, intent);
  75.     }

  76.     @Override
  77.     public DBActionList execute(DBDatabase db) throws SQLException {
  78.         DBRow table = getRow();
  79.         DBActionList actions;
  80.         DBDefinition defn = db.getDefinition();
  81.         try (DBStatement statement = db.getDBStatement()) {
  82.             actions = new DBActionList();
  83.             for (var prop : getInterestingLargeObjects(table)) {
  84.                 final String col = prop.columnName();
  85.                 final DBLargeObject<?> largeObject = (DBLargeObject<?>) prop.getQueryableDatatype();

  86.                 if (largeObject.isNull()) {
  87.                     setToNullUsingStringValue(defn, table, col, largeObject, db, statement);
  88.                 } else {
  89.                     LargeObjectHandlerType handler = defn.preferredLargeObjectWriter(largeObject);
  90.                     switch (handler) {
  91.                         case BLOB:
  92.                             setUsingBLOB(defn, table, col, largeObject, db, statement);
  93.                             break;
  94.                         case BASE64:
  95.                             setUsingBase64String(defn, table, col, largeObject, db, statement);
  96.                             break;
  97.                         case BINARYSTREAM:
  98.                             setUsingBinaryStream(defn, table, col, largeObject, db, statement);
  99.                             break;
  100.                         case CHARSTREAM:
  101.                             setUsingCharacterStream(defn, table, col, largeObject, db, statement);
  102.                             break;
  103.                         case CLOB:
  104.                             setUsingCLOB(defn, table, col, largeObject, db, statement);
  105.                             break;
  106.                         case STRING:
  107.                             setUsingStringValue(defn, table, col, largeObject, db, statement);
  108.                             break;
  109.                         case JAVAOBJECT:
  110.                             setUsingJavaObject(defn, table, col, largeObject, db, statement);
  111.                             break;
  112.                         case BYTE:
  113.                             setUsingByteArray(defn, table, col, largeObject, db, statement);
  114.                             break;
  115.                     }
  116.                 }
  117.                 DBUpdateLargeObjects update = new DBUpdateLargeObjects(table);
  118.                 actions.add(update);
  119.                 largeObject.setUnchanged();
  120.             }
  121.         } catch (SQLException | IOException ex) {
  122.             Logger.getLogger(DBUpdateLargeObjects.class.getName()).log(Level.SEVERE, null, ex);
  123.             throw new DBRuntimeException("Can't Set LargeObject: IOError", ex);
  124.         }
  125.         return actions;
  126.     }

  127.     private void setUsingStringValue(DBDefinition defn, DBRow row, final String col, final DBLargeObject<?> largeObject, DBDatabase db, DBStatement statement) throws SQLException {

  128.         String sqlString = defn.beginUpdateLine()
  129.                 + defn.formatTableName(row)
  130.                 + defn.beginSetClause()
  131.                 + defn.formatColumnName(col)
  132.                 + defn.getEqualsComparator()
  133.                 + " ? "
  134.                 + defn.beginWhereClause()
  135.                 + getPrimaryKeySQL(db, row)
  136.                 + defn.endSQLStatement();
  137.         LOG.debug(sqlString);
  138.         try (PreparedStatement prep = statement.getConnection().prepareStatement(sqlString)) {
  139.             prep.setString(1, largeObject.stringValue());
  140.             prep.execute();
  141.         }

  142.         statement.execute("UPDATING LARGE OBJECTS IN ROW", QueryIntention.UPDATE_ROW, sqlString);
  143.     }

  144.     private void setToNullUsingStringValue(DBDefinition defn, DBRow row, final String col, final DBLargeObject<?> largeObject, DBDatabase db, DBStatement statement) throws SQLException {
  145.         String sqlString = defn.beginUpdateLine()
  146.                 + defn.formatTableName(row)
  147.                 + defn.beginSetClause()
  148.                 + defn.formatColumnName(col)
  149.                 + defn.getEqualsComparator()
  150.                 + defn.getNull()
  151.                 + defn.beginWhereClause()
  152.                 + getPrimaryKeySQL(db, row)
  153.                 + defn.endSQLStatement();
  154.         LOG.debug(sqlString);
  155.         statement.execute("Updating large object to null", QueryIntention.UPDATE_ROW, sqlString);
  156.     }

  157.     private void setUsingBinaryStream(DBDefinition defn, DBRow row, final String col, final DBLargeObject<?> largeObject, DBDatabase db, DBStatement statement) throws SQLException {
  158.         String sqlString = defn.beginUpdateLine()
  159.                 + defn.formatTableName(row)
  160.                 + defn.beginSetClause()
  161.                 + defn.formatColumnName(col)
  162.                 + defn.getEqualsComparator()
  163.                 + defn.getPreparedVariableSymbol()
  164.                 + defn.beginWhereClause()
  165.                 + getPrimaryKeySQL(db, row)
  166.                 + defn.endSQLStatement();
  167.         db.printSQLIfRequested(sqlString);
  168.         LOG.debug(sqlString);
  169.         try (PreparedStatement prep = statement.getConnection().prepareStatement(sqlString)) {
  170.             try {
  171.                 prep.setBinaryStream(1, largeObject.getInputStream());
  172.             } catch (SQLException exp) {
  173.                 try {
  174.                     prep.setBinaryStream(1, largeObject.getInputStream(), largeObject.getSize());
  175.                 } catch (Exception exp2) {
  176.                     throw new DBRuntimeException("Failed to set binary stream", exp);
  177.                 }
  178.             }
  179.             prep.execute();
  180.         }
  181.     }

  182.     private void setUsingBLOB(DBDefinition defn, DBRow row, String col, DBLargeObject<?> largeObject, DBDatabase db, DBStatement statement) throws SQLException, IOException {
  183.         String sqlString = defn.beginUpdateLine()
  184.                 + defn.formatTableName(row)
  185.                 + defn.beginSetClause()
  186.                 + defn.formatColumnName(col)
  187.                 + defn.getEqualsComparator()
  188.                 + defn.getPreparedVariableSymbol()
  189.                 + defn.beginWhereClause()
  190.                 + getPrimaryKeySQL(db, row)
  191.                 + defn.endSQLStatement();
  192.         LOG.debug(sqlString);
  193.         try (PreparedStatement prep = statement.getConnection().prepareStatement(sqlString)) {
  194.             prep.setBlob(1, largeObject.getInputStream(), largeObject.getSize());
  195.             prep.execute();
  196.         }
  197.     }

  198.     private void setUsingCLOB(DBDefinition defn, DBRow row, String col, DBLargeObject<?> largeObject, DBDatabase db, DBStatement statement) throws SQLException, IOException {
  199.         String sqlString = defn.beginUpdateLine()
  200.                 + defn.formatTableName(row)
  201.                 + defn.beginSetClause()
  202.                 + defn.formatColumnName(col)
  203.                 + defn.getEqualsComparator()
  204.                 + defn.getPreparedVariableSymbol()
  205.                 + defn.beginWhereClause()
  206.                 + getPrimaryKeySQL(db, row)
  207.                 + defn.endSQLStatement();
  208.         LOG.debug(sqlString);
  209.         try (PreparedStatement prep = statement.getConnection().prepareStatement(sqlString)) {
  210.             prep.setClob(1, new InputStreamReader(largeObject.getInputStream(), Charset.forName("UTF-8")), largeObject.getSize());
  211.             prep.execute();
  212.         }
  213.     }

  214.     private void setUsingBase64String(DBDefinition defn, DBRow row, final String col, final DBLargeObject<?> largeObject, DBDatabase db, DBStatement statement) throws SQLException, IOException {
  215.         String sqlString = defn.beginUpdateLine()
  216.                 + defn.formatTableName(row)
  217.                 + defn.beginSetClause()
  218.                 + defn.formatColumnName(col)
  219.                 + defn.getEqualsComparator()
  220.                 + defn.getPreparedVariableSymbol()
  221.                 + defn.beginWhereClause()
  222.                 + getPrimaryKeySQL(db, row)
  223.                 + defn.endSQLStatement();
  224.         LOG.debug(sqlString);
  225.         try (PreparedStatement prep = statement.getConnection().prepareStatement(sqlString)) {
  226.             InputStream inputStream = largeObject.getInputStream();

  227.             try (InputStream input = new BufferedInputStream(inputStream)) {
  228.                 List<byte[]> byteArrays = new ArrayList<>();

  229.                 int totalBytesRead = 0;
  230.                 byte[] resultSetBytes;
  231.                 resultSetBytes = new byte[100000];
  232.                 int bytesRead = input.read(resultSetBytes);
  233.                 while (bytesRead > 0) {
  234.                     totalBytesRead += bytesRead;
  235.                     byteArrays.add(resultSetBytes);
  236.                     resultSetBytes = new byte[100000];
  237.                     bytesRead = input.read(resultSetBytes);
  238.                 }
  239.                 byte[] bytes = new byte[totalBytesRead];
  240.                 int bytesAdded = 0;
  241.                 for (byte[] someBytes : byteArrays) {
  242.                     System.arraycopy(someBytes, 0, bytes, bytesAdded, Math.min(someBytes.length, bytes.length - bytesAdded));
  243.                     bytesAdded += someBytes.length;
  244.                 }
  245.                 String b64encoded = Base64.encodeBase64String(bytes);
  246.                 prep.setString(1, b64encoded);
  247.                 prep.execute();
  248.             }
  249.         }
  250.     }

  251.     private void setUsingCharacterStream(DBDefinition defn, DBRow row, final String col, final DBLargeObject<?> largeObject, DBDatabase db, DBStatement statement) throws SQLException, IOError, IOException {
  252.         String sqlString = defn.beginUpdateLine()
  253.                 + defn.formatTableName(row)
  254.                 + defn.beginSetClause()
  255.                 + defn.formatColumnName(col)
  256.                 + defn.getEqualsComparator()
  257.                 + defn.getPreparedVariableSymbol()
  258.                 + defn.beginWhereClause()
  259.                 + getPrimaryKeySQL(db, row)
  260.                 + defn.endSQLStatement();
  261.         db.printSQLIfRequested(sqlString);
  262.         LOG.debug(sqlString);
  263.         try (PreparedStatement prep = statement.getConnection().prepareStatement(sqlString)) {
  264.             InputStream inputStream = largeObject.getInputStream();

  265.             try (InputStreamReader input = new InputStreamReader(inputStream, "UTF-8")) {
  266.                 List<char[]> byteArrays = new ArrayList<>();

  267.                 int totalBytesRead = 0;
  268.                 char[] resultSetBytes;
  269.                 resultSetBytes = new char[100000];
  270.                 int bytesRead = input.read(resultSetBytes);
  271.                 while (bytesRead > 0) {
  272.                     totalBytesRead += bytesRead;
  273.                     byteArrays.add(resultSetBytes);
  274.                     resultSetBytes = new char[100000];
  275.                     bytesRead = input.read(resultSetBytes);
  276.                 }
  277.                 char[] bytes = new char[totalBytesRead];
  278.                 int bytesAdded = 0;
  279.                 for (char[] someBytes : byteArrays) {
  280.                     System.arraycopy(someBytes, 0, bytes, bytesAdded, Math.min(someBytes.length, bytes.length - bytesAdded));
  281.                     bytesAdded += someBytes.length;
  282.                 }
  283.                 prep.setCharacterStream(1, new BufferedReader(new CharArrayReader(bytes)), bytes.length);
  284.                 prep.execute();
  285.             }
  286.         }
  287.     }

  288.     @Override
  289.     public List<String> getSQLStatements(DBDatabase db) {
  290.         List<String> strs = new ArrayList<>();
  291.         strs.add(db.getDefinition().startMultilineComment() + " SAVE BINARY DATA" + db.getDefinition().endMultilineComment());
  292.         return strs;
  293.     }

  294.     /**
  295.      * Finds all the DBLargeObject fields that this action will need to update.
  296.      *
  297.      * @param row the row to be updated
  298.      * <p style="color: #F90;">Support DBvolution at
  299.      * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
  300.      * @return a list of the interesting DBLargeObjects.
  301.      */
  302.     protected List<PropertyWrapper<?, ?, ?>> getInterestingLargeObjects(DBRow row) {
  303.         return getChangedLargeObjects(row);
  304.     }

  305.     @Override
  306.     protected DBActionList getRevertDBActionList() {
  307.         return new DBActionList();
  308.     }

  309.     private List<PropertyWrapper<?, ?, ?>> getChangedLargeObjects(DBRow row) {
  310.         List<PropertyWrapper<?, ?, ?>> changed = new ArrayList<>();
  311.         if (row.hasLargeObjects()) {
  312.             for (QueryableDatatype<?> qdt : row.getLargeObjects()) {
  313.                 if (qdt instanceof DBLargeObject) {
  314.                     DBLargeObject<?> large = (DBLargeObject<?>) qdt;
  315.                     if (large.hasChanged()) {
  316.                         changed.add(row.getPropertyWrapperOf(qdt));
  317.                     }
  318.                 }
  319.             }
  320.         }
  321.         return changed;
  322.     }

  323.     private void setUsingJavaObject(DBDefinition defn, DBRow row, String col, DBLargeObject<?> largeObject, DBDatabase db, DBStatement statement) throws SQLException {
  324.         String sqlString = defn.beginUpdateLine()
  325.                 + defn.formatTableName(row)
  326.                 + defn.beginSetClause()
  327.                 + defn.formatColumnName(col)
  328.                 + defn.getEqualsComparator()
  329.                 + defn.getPreparedVariableSymbol()
  330.                 + defn.beginWhereClause()
  331.                 + getPrimaryKeySQL(db, row)
  332.                 + defn.endSQLStatement();
  333.         LOG.debug(sqlString);
  334.         try (PreparedStatement prep = statement.getConnection().prepareStatement(sqlString)) {
  335.             prep.setObject(1, largeObject.getValue());
  336.             prep.execute();
  337.         }
  338.     }

  339.     private void setUsingByteArray(DBDefinition defn, DBRow row, String col, DBLargeObject<?> largeObject, DBDatabase db, DBStatement statement) throws SQLException, IOException {
  340.         String sqlString = defn.beginUpdateLine()
  341.                 + defn.formatTableName(row)
  342.                 + defn.beginSetClause()
  343.                 + defn.formatColumnName(col)
  344.                 + defn.getEqualsComparator()
  345.                 + defn.getPreparedVariableSymbol()
  346.                 + defn.beginWhereClause()
  347.                 + getPrimaryKeySQL(db, row)
  348.                 + defn.endSQLStatement();
  349.         LOG.debug(sqlString);
  350.         try (PreparedStatement prep = statement.getConnection().prepareStatement(sqlString)) {
  351.             InputStream inputStream = largeObject.getInputStream();

  352.             try (InputStream input = new BufferedInputStream(inputStream)) {
  353.                 List<byte[]> byteArrays = new ArrayList<>();

  354.                 int totalBytesRead = 0;
  355.                 byte[] resultSetBytes;
  356.                 resultSetBytes = new byte[100000];
  357.                 int bytesRead = input.read(resultSetBytes);
  358.                 while (bytesRead > 0) {
  359.                     totalBytesRead += bytesRead;
  360.                     byteArrays.add(resultSetBytes);
  361.                     resultSetBytes = new byte[100000];
  362.                     bytesRead = input.read(resultSetBytes);
  363.                 }
  364.                 byte[] bytes = new byte[totalBytesRead];
  365.                 int bytesAdded = 0;
  366.                 for (byte[] someBytes : byteArrays) {
  367.                     System.arraycopy(someBytes, 0, bytes, bytesAdded, Math.min(someBytes.length, bytes.length - bytesAdded));
  368.                     bytesAdded += someBytes.length;
  369.                 }
  370.                 prep.setBytes(1, bytes);
  371.                 prep.execute();
  372.             }
  373.         }
  374.     }
  375. }