1 /* 2 Copyright (c) 2010, 2021, Oracle and/or its affiliates. 3 4 This program is free software; you can redistribute it and/or modify 5 it under the terms of the GNU General Public License, version 2.0, 6 as published by the Free Software Foundation. 7 8 This program is also distributed with certain software (including 9 but not limited to OpenSSL) that is licensed under separate terms, 10 as designated in a particular file or component or in included license 11 documentation. The authors of MySQL hereby grant you an additional 12 permission to link the program and your derivative works with the 13 separately licensed software that they have included with MySQL. 14 15 This program is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 GNU General Public License, version 2.0, for more details. 19 20 You should have received a copy of the GNU General Public License 21 along with this program; if not, write to the Free Software 22 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 23 */ 24 25 package com.mysql.clusterj.jpatest; 26 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.nio.charset.Charset; 30 import java.nio.charset.CharsetEncoder; 31 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.List; 35 36 import com.mysql.clusterj.jpatest.model.ClobTypes; 37 38 /** Test clobs 39 * Schema: 40 drop table if exists charsetlatin1; 41 create table charsetlatin1 ( 42 id int not null primary key, 43 smallcolumn varchar(200), 44 mediumcolumn varchar(500), 45 largecolumn text(10000) 46 47 ) ENGINE=ndbcluster DEFAULT CHARSET=latin1; 48 49 */ 50 public class ClobTest extends AbstractJPABaseTest { 51 52 /** The size of the clob column is 2 raised to the power i. */ 53 private static final int NUMBER_TO_INSERT = 16; 54 55 /** The clob instances for testing. */ 56 protected List<ClobTypes> clobs = new ArrayList<ClobTypes>(); 57 58 /** Characters that can be encoded using latin1 character set */ 59 private char[] chars = getChars(Charset.forName("latin1")); 60 61 /** Subclasses can override this method to get debugging info printed to System.out */ getDebug()62 protected boolean getDebug() { 63 return false; 64 } 65 66 /** Create a char array with the first 128 encodable characters. 67 * 68 * @return the char array 69 */ getChars(Charset charset)70 private char[] getChars(Charset charset) { 71 CharsetEncoder encoder = charset.newEncoder(); 72 char[] result = new char[128]; 73 char current = (char)0; 74 for (int i = 0; i < result.length; ++i) { 75 current = nextChar(encoder, current); 76 result[i] = current; 77 } 78 return result; 79 } 80 81 /** Get the next char greater than the current char that is encodable 82 * by the encoder. 83 * @param encoder 84 * 85 */ nextChar(CharsetEncoder encoder, char current)86 char nextChar(CharsetEncoder encoder, char current) { 87 current++; 88 if (encoder.canEncode(current)) { 89 return current; 90 } else { 91 return nextChar(encoder, current); 92 } 93 } 94 test()95 public void test() { 96 createClobInstances(NUMBER_TO_INSERT); 97 remove(); 98 insert(); 99 update(); 100 failOnError(); 101 } 102 remove()103 protected void remove() { 104 removeAll(ClobTypes.class); 105 } 106 insert()107 protected void insert() { 108 // insert instances 109 tx = em.getTransaction(); 110 tx.begin(); 111 112 int count = 0; 113 114 for (int i = 0; i < NUMBER_TO_INSERT; ++i) { 115 // must be done with an active transaction 116 em.persist(clobs.get(i)); 117 ++count; 118 } 119 tx.commit(); 120 } 121 update()122 protected void update() { 123 124 tx.begin(); 125 126 for (int i = 1; i < NUMBER_TO_INSERT; ++i) { 127 // must be done with an active transaction 128 ClobTypes e = em.find(ClobTypes.class, i); 129 // see if it is the right one 130 int actualId = e.getId(); 131 if (actualId != i) { 132 error("Expected ClobTypes.id " + i + " but got " + actualId); 133 } 134 String string = e.getLarge10000(); 135 // make sure all fields were fetched properly 136 checkString("before update", string, i, false); 137 138 int position = getClobSizeFor(i)/2; 139 // only update if the length is correct 140 if (string.length() == (position * 2)) { 141 StringBuilder sb = new StringBuilder(string); 142 // modify the byte in the middle of the blob 143 sb.replace(position, position + 1, "!"); 144 string = sb.toString(); 145 checkString("after update", string, i, true); 146 } 147 e.setLarge10000(string); 148 } 149 tx.commit(); 150 tx.begin(); 151 152 for (int i = 1; i < NUMBER_TO_INSERT; ++i) { 153 // must be done with an active transaction 154 ClobTypes e = em.find(ClobTypes.class, i); 155 // see if it is the right one 156 int actualId = e.getId(); 157 if (actualId != i) { 158 error("Expected ClobTypes.id " + i + " but got " + actualId); 159 } 160 String string = e.getLarge10000(); 161 162 // check to see that the blob field has the right data 163 checkString("after commit", string, i, true); 164 } 165 tx.commit(); 166 } 167 createClobInstances(int number)168 protected void createClobInstances(int number) { 169 for (int i = 0; i < number; ++i) { 170 ClobTypes instance = new ClobTypes(); 171 instance.setId(i); 172 int length = getClobSizeFor(i); 173 instance.setLarge10000(getString(length)); 174 // blob streams are not yet supported 175 // instance.setBlobstream(getBlobStream(length)); 176 clobs.add(instance); 177 } 178 } 179 180 /** Create a new String of the specified size containing a pattern 181 * of characters in which each character is the value of encodable characters 182 * at position modulo 128. This pattern is easy to test. 183 * @param size the length of the returned string 184 * @return the string filled with the pattern 185 */ getString(int size)186 protected String getString(int size) { 187 char[] result = new char[size]; 188 for (int i = 0; i < size; ++i) { 189 result[i] = chars [i%128]; 190 } 191 return new String(result); 192 } 193 194 /** Check the string to be sure it matches the pattern in both size 195 * and contents. 196 * @see getString 197 * @param string the string to check 198 * @param number the expected length of the string 199 * @param updated whether the string is original or updated 200 */ checkString(String where, String string, int number, boolean updated)201 protected void checkString(String where, String string, int number, boolean updated) { 202 if (getDebug()) dumpClob(where, string); 203 int expectedSize = getClobSizeFor(number); 204 int actualSize = string.length(); 205 if (expectedSize != actualSize) { 206 error("In " + where 207 + " wrong size of string; " 208 + "expected: " + expectedSize 209 + " actual: " + actualSize); 210 } 211 for (int i = 0; i < actualSize; ++i) { 212 char expected; 213 int position = expectedSize/2; 214 if (updated && (i == position)) { 215 expected = '!'; 216 } else { 217 expected = chars[i%128]; 218 } 219 char actual = string.charAt(i); 220 if (expected != actual) { 221 error("In " + where + " for size: " + actualSize 222 + " mismatch in string at position " + i 223 + " expected: " + (int)expected 224 + " actual: " + (int)actual); 225 } 226 } 227 228 } 229 getClobStream(final int i)230 protected InputStream getClobStream(final int i) { 231 return new InputStream() { 232 int size = i; 233 int counter = 0; 234 @Override 235 public int read() throws IOException { 236 if (counter >= i) { 237 return -1; 238 } else { 239 return counter++ %256; 240 } 241 } 242 }; 243 } 244 dumpClob(String where, String string)245 protected void dumpClob(String where, String string) { 246 System.out.println("In " + where + " dumpClob of size: " + string.length() + " " + Arrays.toString(string.getBytes())); 247 } 248 getClobSizeFor(int i)249 protected int getClobSizeFor(int i) { 250 int length = (int) Math.pow(2, i); 251 return length; 252 } 253 254 } 255