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