66 */
77package com .oracle .nosql .spring .data .core ;
88
9+ import java .util .ArrayList ;
910import java .util .HashMap ;
1011import java .util .LinkedHashMap ;
12+ import java .util .List ;
1113import java .util .Map ;
14+ import java .util .stream .Collectors ;
1215
1316import oracle .nosql .driver .NoSQLException ;
1417import oracle .nosql .driver .NoSQLHandle ;
1518import oracle .nosql .driver .TableNotFoundException ;
19+ import oracle .nosql .driver .TimeToLive ;
1620import oracle .nosql .driver .ops .DeleteRequest ;
1721import oracle .nosql .driver .ops .DeleteResult ;
1822import oracle .nosql .driver .ops .GetRequest ;
1923import oracle .nosql .driver .ops .GetResult ;
24+ import oracle .nosql .driver .ops .GetTableRequest ;
2025import oracle .nosql .driver .ops .PrepareRequest ;
2126import oracle .nosql .driver .ops .PrepareResult ;
2227import oracle .nosql .driver .ops .PreparedStatement ;
2631import oracle .nosql .driver .ops .TableRequest ;
2732import oracle .nosql .driver .ops .TableResult ;
2833import oracle .nosql .driver .util .LruCache ;
34+ import oracle .nosql .driver .values .ArrayValue ;
2935import oracle .nosql .driver .values .FieldValue ;
36+ import oracle .nosql .driver .values .JsonOptions ;
37+ import oracle .nosql .driver .values .JsonUtils ;
3038import oracle .nosql .driver .values .MapValue ;
3139
3240import com .oracle .nosql .spring .data .NosqlDbFactory ;
@@ -112,9 +120,7 @@ protected TableResult doTableRequest(NosqlEntityInformation<?, ?> entityInformat
112120 return tableRes ;
113121 }
114122
115- protected boolean doCreateTableIfNotExists (
116- NosqlEntityInformation <?, ?> entityInformation ) {
117-
123+ protected String getCreateTableDDL (NosqlEntityInformation <?, ?> entityInformation ) {
118124 String tableName = entityInformation .getTableName ();
119125 String sql ;
120126
@@ -168,8 +174,12 @@ protected boolean doCreateTableIfNotExists(
168174 entityInformation .getTtl ().toString ()));
169175 }
170176 sql = tableBuilder .toString ();
177+ return sql ;
178+ }
171179
172- TableRequest tableReq = new TableRequest ().setStatement (sql )
180+ protected boolean doCreateTable (NosqlEntityInformation <?, ?> entityInformation ) {
181+ String ddl = getCreateTableDDL (entityInformation );
182+ TableRequest tableReq = new TableRequest ().setStatement (ddl )
173183 .setTableLimits (entityInformation .getTableLimits (nosqlDbFactory ));
174184
175185 TableResult tableRes = doTableRequest (entityInformation , tableReq );
@@ -178,6 +188,179 @@ protected boolean doCreateTableIfNotExists(
178188 return tableState == TableResult .State .ACTIVE ;
179189 }
180190
191+ protected boolean doCheckExistingTable (NosqlEntityInformation <?, ?> entityInformation ) {
192+ final String colField = "fields" ;
193+ final String colNameField = "name" ;
194+ final String colTypeField = "type" ;
195+ final String shardField = "shardKey" ;
196+ final String primaryField = "primaryKey" ;
197+ final String ttlField = "ttl" ;
198+ final String identityField = "identity" ;
199+
200+ List <String > errors = new ArrayList <>();
201+
202+ TableResult tableResult = null ;
203+ String jsonSchema = null ;
204+ try {
205+ tableResult = doGetTable (entityInformation );
206+
207+ // table does not exist return false
208+ if (tableResult == null ) {
209+ return false ;
210+ }
211+
212+ /* If table already exist in the database compare and throw error if
213+ mismatch*/
214+ jsonSchema = tableResult .getSchema ();
215+ MapValue tableSchema = JsonUtils .createValueFromJson (jsonSchema ,
216+ new JsonOptions ().setMaintainInsertionOrder (true )).
217+ asMap ();
218+
219+ ArrayValue tableColumns = tableSchema .get (colField ).asArray ();
220+ ArrayValue tableShardKeys =
221+ tableSchema .get (shardField ).asArray ();
222+ ArrayValue tablePrimaryKeys =
223+ tableSchema .get (primaryField ).asArray ();
224+
225+ Map <String , String > tableShardMap = new LinkedHashMap <>();
226+ Map <String , String > tableNonShardMap = new LinkedHashMap <>();
227+ Map <String , String > tableOthersMap = new LinkedHashMap <>();
228+
229+ // extract table details into maps
230+ for (int i = 0 ; i < tableColumns .size (); i ++) {
231+ MapValue column = tableColumns .get (i ).asMap ();
232+ String colName =
233+ column .getString (colNameField ).toLowerCase ();
234+ String columnType =
235+ column .getString (colTypeField ).toLowerCase ();
236+
237+ if (i < tableShardKeys .size ()) {
238+ tableShardMap .put (colName , columnType );
239+ } else if (i < tablePrimaryKeys .size ()) {
240+ tableNonShardMap .put (colName , columnType );
241+ } else {
242+ tableOthersMap .put (colName , columnType );
243+ }
244+ }
245+
246+ // extract entity details into maps
247+ Map <String , FieldValue .Type > shardKeys = entityInformation .
248+ getShardKeys ();
249+ Map <String , FieldValue .Type > nonShardKeys = entityInformation .
250+ getNonShardKeys ();
251+
252+ Map <String , String > entityShardMap = new LinkedHashMap <>();
253+ shardKeys .forEach ((k , v ) -> entityShardMap .put (
254+ k .toLowerCase (), v .name ().toLowerCase ()));
255+
256+ Map <String , String > entityNonShardMap = new LinkedHashMap <>();
257+ nonShardKeys .forEach ((k , v ) -> entityNonShardMap .put (
258+ k .toLowerCase (), v .name ().toLowerCase ()));
259+
260+ Map <String , String > entityOthersMap = new LinkedHashMap <>();
261+ entityOthersMap .put (JSON_COLUMN .toLowerCase (), "json" );
262+
263+ // convert maps to String. String format is {k1 v1, k2 v2 ...}
264+ String tableShards = "{" + tableShardMap .entrySet ().stream ()
265+ .map (e -> e .getKey () + " " + e .getValue ()).
266+ collect (Collectors .joining ("," )) + "}" ;
267+ String tableNonShards = "{" + tableNonShardMap .entrySet ()
268+ .stream ().map (e -> e .getKey () + " " + e .getValue ()).
269+ collect (Collectors .joining ("," )) + "}" ;
270+
271+ String entityShards = "{" + entityShardMap .entrySet ().stream ()
272+ .map (e -> e .getKey () + " " + e .getValue ()).
273+ collect (Collectors .joining ("," )) + "}" ;
274+ String entityNonShards = "{" + entityNonShardMap .entrySet ()
275+ .stream ().map (e -> e .getKey () + " " + e .getValue ()).
276+ collect (Collectors .joining ("," )) + "}" ;
277+
278+ String msg ;
279+ // check shard keys and types match
280+ if (!tableShards .equals (entityShards )) {
281+ msg = String .format ("Shard primary keys mismatch: " +
282+ "table=%s, entity=%s." , tableShards , entityShards );
283+ errors .add (msg );
284+ }
285+
286+ // check non-shard keys and types match
287+ if (!tableNonShards .equals (entityNonShards )) {
288+ msg = String .format ("Non-shard primary keys mismatch: " +
289+ "table=%s, entity=%s." , tableNonShards ,
290+ entityNonShards );
291+ errors .add (msg );
292+ }
293+
294+ // check kv_json_ column exist and it's type is JSON
295+ if (!tableOthersMap .containsKey (JSON_COLUMN .toLowerCase ())) {
296+ msg = String .format ("'%s' column does not exist in the table" ,
297+ JSON_COLUMN );
298+ errors .add (msg );
299+ } else if (!tableOthersMap .get (JSON_COLUMN .toLowerCase ()).
300+ equalsIgnoreCase ("json" )) {
301+ msg = String .format ("'%s' column type is not JSON in the " +
302+ "table" , JSON_COLUMN );
303+ errors .add (msg );
304+ }
305+
306+ // check identity same
307+ FieldValue identity = tableSchema .get (identityField );
308+ if (identity != null && !entityInformation .isAutoGeneratedId ()) {
309+ errors .add ("Identity information mismatch." );
310+
311+ } else if (identity == null && entityInformation .isAutoGeneratedId () &&
312+ entityInformation .getIdNosqlType () != FieldValue .Type .STRING ) {
313+ errors .add ("Identity information mismatch." );
314+ }
315+
316+ // TTL warning
317+ FieldValue ttlValue = tableSchema .get (ttlField );
318+ TimeToLive ttl = entityInformation .getTtl ();
319+ // TTL is present in database but not in the entity
320+ if (ttlValue != null && ttl != null &&
321+ !ttl .toString ().equalsIgnoreCase (ttlValue .getString ())) {
322+ LOG .warn ("TTL of the table in database is different from " +
323+ "the TTL of the entity " +
324+ entityInformation .getJavaType ().getName ());
325+ } else if (ttlValue == null && ttl != null && ttl .getValue () != 0 ) {
326+ // TTL is present in entity but not in the database
327+ LOG .warn ("TTL of the table in database is different from " +
328+ "the TTL of the entity " +
329+ entityInformation .getJavaType ().getName ());
330+ }
331+ } catch (NullPointerException | ClassCastException ex ) {
332+ // something is wrong in parsing json schema
333+ String msg = String .format ("Could not validate schema of the " +
334+ "table %s in the database against entity %s : " +
335+ "%s" ,
336+ entityInformation .getTableName (),
337+ entityInformation .getJavaType ().getName (),
338+ ex .getMessage ());
339+
340+ if (LOG .isDebugEnabled ()) {
341+ LOG .debug ("JSON Schema of the table is " + jsonSchema );
342+ }
343+ LOG .warn (msg );
344+ }
345+
346+ if (!errors .isEmpty ()) {
347+ StringBuilder sb = new StringBuilder ();
348+ sb .append ("The following mismatch have been found between the " +
349+ "entity class " );
350+ sb .append (entityInformation .getJavaType ().getName ());
351+ sb .append (" definition and the existing table " );
352+ sb .append (entityInformation .getTableName ());
353+ sb .append (" in the database:\n " );
354+ errors .forEach (err -> sb .append (err ).append ("\n " ));
355+ sb .append ("To fix this errors, either make the entity class to" +
356+ " match the table definition or use NosqlTable.name" +
357+ " annotation to use a different table." );
358+ throw new IllegalArgumentException (sb .toString ());
359+ }
360+ // no mismatch between table and entity return true
361+ return true ;
362+ }
363+
181364 protected DeleteResult doDelete (
182365 NosqlEntityInformation <?, ?> entityInformation ,
183366 MapValue primaryKey ) {
@@ -229,16 +412,7 @@ protected PutResult doPut(NosqlEntityInformation<?, ?> entityInformation,
229412
230413 PutResult putRes ;
231414 try {
232- try {
233- putRes = nosqlClient .put (putReq );
234- } catch (TableNotFoundException tnfe ) {
235- if (entityInformation .isAutoCreateTable ()) {
236- doCreateTableIfNotExists (entityInformation );
237- putRes = nosqlClient .put (putReq );
238- } else {
239- throw tnfe ;
240- }
241- }
415+ putRes = nosqlClient .put (putReq );
242416 } catch (NoSQLException nse ) {
243417 LOG .error ("Put: table: {} key: {}" , putReq .getTableName (),
244418 row .get (entityInformation .getIdColumnName ()));
@@ -378,6 +552,18 @@ protected <T> Iterable<MapValue> doExecuteMapValueQuery(NosqlQuery query,
378552 return doQuery (qReq );
379553 }
380554
555+ protected TableResult doGetTable (NosqlEntityInformation <?, ?> entityInformation ) {
556+ try {
557+ GetTableRequest request = new GetTableRequest ();
558+ request .setTableName (entityInformation .getTableName ());
559+ return nosqlClient .getTable (request );
560+ } catch (TableNotFoundException tne ) {
561+ return null ;
562+ } catch (NoSQLException nse ) {
563+ throw MappingNosqlConverter .convert (nse );
564+ }
565+ }
566+
381567 private PreparedStatement getPreparedStatement (
382568 NosqlEntityInformation <?, ?> entityInformation , String query ) {
383569 PreparedStatement preparedStatement ;
0 commit comments