Project

General

Profile

direct_access.patch

Alexandru Lungu, 02/10/2023 09:54 AM

Download (74.9 KB)

View differences:

new/build.gradle 2023-02-09 11:42:31 +0000
237 237
repositories {
238 238
    // https://repo1.maven.org/maven2/
239 239
    mavenCentral()
240

  
241
    flatDir {
242
       dirs("custom")
243
    }
240 244
    
241 245
    maven { 
242 246
       name "maven2"
......
441 445
    fwdCCS group: 'org.apache.commons', name: 'commons-collections4', version: '4.4'
442 446
    fwdConvertServer group: 'org.jboss.logging', name: 'jboss-logging', version: '3.1.0.GA'
443 447
    fwdConvertServer group: 'antlr', name: 'antlr', version: '2.7.7'
444
    fwdConvertServer group: 'com.goldencode', name: 'fwd-h2', version: '1.4.200-6'
448
    // fwdConvertServer group: 'com.goldencode', name: 'fwd-h2', version: '1.4.200-6'
449
    fwdConvertServer files('custom/myh2.jar')
445 450
    fwdConvertServer group: 'javax.transaction', name: 'jta', version: '1.1'
446 451
    fwdConvertServer group: 'org.janusgraph', name: 'janusgraph-core', version: '0.1.0'
447 452

  
new/src/com/goldencode/p2j/jmx/FwdJMX.java 2023-02-10 13:59:34 +0000
14 14
**     IAS 20220626 Added SSL counters.
15 15
**     AL2 20220828 Added H2 Query Profiler.
16 16
**     CA  20221006 Added instrumentation for some APIs in the ORM and persistence layer.  Refs #6814
17
*     CA  20221031 Added more instrumentation for:
17
*      CA  20221031 Added more instrumentation for:
18 18
**                  - the 'execute' time at H2 statements (OrmTempTableQuery).
19 19
**                  - ContextLocal.get (ContextLocalGet).
20 20
**                  - TableMapper.mapTemporaryTable (OrmTableMapperMap).
......
25 25
**     AL2 20230104 Added OrmRedo.
26 26
**                  Added OrmNoUndoTrace.
27 27
**     OM  20230112 Added finer-granulation instrumentation for processing of dynamic queries. 
28
**     AL2 20230210 Added instrumentation for RandomAccessQuery resolutions: fast-find, direct access, SQL.
28 29
*/ 
29 30

  
30 31
/*
......
291 292
      /** Instrumentation for <code>TableMapper.mapTemporaryTable</code>. */
292 293
      OrmTableMapperMap,
293 294
      
294
      
295 295
      /** Instrumentation for <code>TemporaryBuffer.associate</code>. */
296 296
      OrmTempTableParam,
297 297
      /** Instrumentation for <code>TemporaryBuffer.createDynamicTable</code>. */
......
301 301
      /** Instrumentation for <code>DataSet.createDynamicDataSet</code>. */
302 302
      OrmDynamicDataSetParam,
303 303

  
304
      /** Instrumentation for <code>RandomAccessQuery</code> fast-find cache hits. */
305
      OrmFFCacheHit,
306
      /** Instrumentation for <code>RandomAccessQuery</code> fast-find cache hits with no record. */
307
      OrmFFCacheHitNoRecord,
308
      /** Instrumentation for <code>RandomAccessQuery</code> direct access technique over unique index. */
309
      OrmFindDirectAccess,
310
      /** Instrumentation for <code>RandomAccessQuery</code> direct access technique over recid */
311
      OrmFindDirectAccessPk,
312
      /** Instrumentation for <code>RandomAccessQuery.executeImpl</code> */
313
      OrmFindExecute,
314

  
304 315
      /** Instrumentation for <code>OutputTableCopier.finished</code> */
305 316
      OutputTableCopier,
306 317
      /** Instrumentation for <code>OutputTableHandleCopier.finished</code> */
new/src/com/goldencode/p2j/persist/FQLPreprocessor.java 2023-02-10 14:06:39 +0000
282 282
**                           are not the same), to be aware of the external buffers (i.e. added for CAN-FIND
283 283
**                           sub-select clauses).  The translate will be performed before the query is being
284 284
**                           executed.
285
**     AL2 20230210          Use prop matches to identify if the where clause is a look-up on unique index.
285 286
*/
286 287

  
287 288
/*
......
630 631
   /** Flag the query that it should performed on recid/rowid lookup table. */
631 632
   private boolean findByRowid = false;
632 633
   
634
   /** Helper that helps indentifying if this where clause is in fact a unique index look-up. */
635
   private UniqueIndexLookup uniqueIndexLookup = null;
636
   
633 637
   /** 
634 638
    * The actual {@code rowid} value that is to be loaded if it is a constant detected in 
635 639
    * {@code fql} preprocessing. If it is {@code null} but {@code findByRowid = true} then
......
1280 1284
         // character datatype special case
1281 1285
         return new int64(((BaseDataType) params)).longValue();
1282 1286
      }
1283
      
1287
      else if (params instanceof Integer)
1288
      {
1289
         return ((Integer) params).longValue();
1290
      }
1291
      else if (params instanceof Long)
1292
      {
1293
         return ((Long) params).longValue();
1294
      }
1295
         
1284 1296
      if (LOG.isLoggable(Level.WARNING))
1285 1297
      {
1286 1298
         LOG.log(Level.WARNING, "Incompatible query parameters");
......
1289 1301
   }
1290 1302
   
1291 1303
   /**
1304
    * Check if the where clause is a conjunction of equality clauses that strictly cover an 
1305
    * unique index. This way, it is guaranteed that there is zero or one record fetched at 
1306
    * the end.
1307
    * 
1308
    * @param    includeRowid
1309
    *           {@code true} if the function should consider rowid as a field of an unique index.
1310
    *           
1311
    * @return   {@code true} if the target where clause if for zero or one record due to 
1312
    *           an unique index look-up.
1313
    */
1314
   public boolean isUniqueFind(boolean includeRowid)
1315
   {
1316
      return uniqueIndexLookup != null || (includeRowid ? isFindByRowid() : false);
1317
   }
1318
   
1319
   /**
1320
    * Retrieve the unique index loop-up object, responsible for storing information on the
1321
    * unique index that is used. Note that the properties form a unique index, but not necessarily
1322
    * in the provided order. Also, some values may be null due to the fact that they are substitution.
1323
    * 
1324
    * @return   an object which may assist faster look-up based on unique indexes
1325
    */
1326
   public UniqueIndexLookup getUniqueIndexLookup()
1327
   {
1328
      return uniqueIndexLookup;
1329
   }
1330
   
1331
   /**
1292 1332
    * Get the <code>ParameterIndices</code> object associated with this query.
1293 1333
    * This object maintains an array of zero-based indices into the array of
1294 1334
    * query substitution arguments for the where clause, as it exists
......
1491 1531
            annotateParameters(root, paramTypes);
1492 1532
         }
1493 1533
         
1494
         if (informational && parameters != null)
1495
         {
1496
            // at the moment, this method is only interesting for server-side join optimization
1497
            // analysis, which presumes at least one field reference parameter; if this becomes
1498
            // more useful generally, we can remove the conditional test above
1499
            collectPropertyMatches(root);
1500
         }
1534
         //if (informational && parameters != null)
1535
         // at the moment, this method is only interesting for server-side join optimization
1536
         // analysis, which presumes at least one field reference parameter; if this becomes
1537
         // more useful generally, we can remove the conditional test above
1538
         // it is useful for H2 direct access to identify queries on unique indexes
1539
         collectPropertyMatches(root);
1501 1540
         
1502 1541
         // simplify the predicate in raport to boolean values
1503 1542
         root = processCompareToUnknowns(root, parameters, indexedFields);
......
1554 1593
         
1555 1594
         this.findByRowid = checkFindByRowid(root);
1556 1595
         
1596
         this.uniqueIndexLookup = checkUniqueFind();
1597
         
1557 1598
         // Emit the preprocessed FQL expression text.
1558 1599
         this.fql = emit(root, false);
1559 1600
         
......
2086 2127
      return false;
2087 2128
   }
2088 2129
   
2130

  
2131
   
2132
   /**
2133
    * Check if the where clause is a look-up over a unique index.
2134
    * <p>
2135
    * The goal here is to identify a common business logic pattern: find by an unique index, usually
2136
    * primary. The implementation doesn't detect if the where clauses combination suggest that 
2137
    * zero or one record is expected. It just checks if the property matches match an unique 
2138
    * index. Callers rely on this. Extending the method with heuristics for uniqueness beyond
2139
    * unique indexes may cause problems.
2140
    * 
2141
    * @return   {@code true} if the where clause is a conjunction of equalities covering all
2142
    *           the fields of an unique index.
2143
    */
2144
   private UniqueIndexLookup checkUniqueFind()
2145
   {      
2146
      List<PropertyMatch> matches = getPropertyMatches();
2147
      if (matches == null)
2148
      {
2149
         return null;
2150
      }
2151
      
2152
      RecordBuffer targetBuffer = null;
2153
      List<String> properties = new ArrayList<>();
2154
      for (int i = 0; i < matches.size(); i++)
2155
      {
2156
         PropertyMatch match = matches.get(i);
2157
         
2158
         if (match.operator != EQUALS)
2159
         {
2160
            return null;
2161
         }
2162
         
2163
         RecordBuffer buffer = DataTypeHelper.lookupBuffer(match.alias, bufferMap, fql);
2164
         if (buffer == null)
2165
         {
2166
            // this is unexpected; return false for fault tolerance
2167
            return null;
2168
         }
2169
         
2170
         if (targetBuffer == null)
2171
         {
2172
            targetBuffer = buffer;
2173
         }
2174
         else if (targetBuffer != buffer)
2175
         {
2176
            // mixed where clauses
2177
            return null;
2178
         }
2179

  
2180
         properties.add(match.property);
2181
      }
2182
      
2183
      final DmoMeta meta = targetBuffer.getDmoInfo();
2184
      final Dialect dialect = targetBuffer.getDialect();
2185
      Function<String, String> honorComputed = (originalProp) -> 
2186
      {
2187
         Property propMeta = meta.getFieldInfo(originalProp);
2188
         if (propMeta._isCharacter && dialect.needsComputedColumns())
2189
         {
2190
            Boolean ccIgnoreCase = meta.isIndexedIgnoreCase(originalProp);
2191
            if (ccIgnoreCase != null)
2192
            {
2193
               return dialect.getComputedColumnPrefix(!ccIgnoreCase) + originalProp;
2194
            }
2195
         }
2196
         
2197
         return originalProp;
2198
      };
2199
      
2200
      Iterator<Set<String>> uniques = meta.getUniqueConstraints().iterator();
2201
      while (uniques.hasNext())
2202
      {
2203
         Set<String> compSet = uniques.next();
2204
         // match exactly an unique index; 
2205
         // having less fields doesn't make the where unique
2206
         // having more fields allows the where clause to return zero records even if a record is 
2207
         // found according to the unqiue index
2208
         if (compSet.size() == properties.size() && properties.containsAll(compSet))
2209
         {
2210
            UniqueIndexLookup lookup = new UniqueIndexLookup();
2211
            for (int i = 0; i < matches.size(); i++)
2212
            {
2213
               PropertyMatch match = matches.get(i);
2214
               Object arg = null;
2215
               switch (match.rvalType)
2216
               {
2217
                  case SUBST:
2218
                     arg = null;
2219
                     break;
2220
                  case BOOL_TRUE:
2221
                  case BOOL_FALSE:
2222
                     arg = match.rvalType == BOOL_TRUE;
2223
                     break;
2224
                  case NUM_LITERAL:
2225
                     arg = Long.parseLong(match.rval);
2226
                     break;
2227
                  case STRING:
2228
                     if (match.rval.startsWith("'") && match.rval.endsWith("'"))
2229
                     {
2230
                        arg = match.rval.substring(1,  match.rval.length() - 1);
2231
                     }
2232
                     else
2233
                     {
2234
                        // panic
2235
                        return null;
2236
                     }
2237
                     break;
2238
                  case DEC_LITERAL:
2239
                     arg = Double.parseDouble(match.rval);
2240
                     break;
2241
                  default:
2242
                     return null; // safety
2243
               }
2244
               String sqlProp = honorComputed.apply(match.property);
2245
               lookup.addMapping(sqlProp, arg);
2246
            }
2247
            return lookup;
2248
         }
2249
      }
2250
      
2251
      return null;
2252
   }
2253
   
2089 2254
   /**
2090 2255
    * Scan and parse the input where clause and return it in the form of an
2091 2256
    * abstract syntax tree.  Rewriting is not performed at this stage.
new/src/com/goldencode/p2j/persist/RandomAccessQuery.java 2023-02-10 14:18:23 +0000
347 347
**                           are not the same), to be aware of the external buffers (i.e. added for CAN-FIND
348 348
**                           sub-select clauses).  The translate will be performed before the query is being
349 349
**                           executed.
350
**     AL2 20230210          Allow direct access for recid/unique index look-ups.
350 351
*/
351 352

  
352 353
/*
......
409 410
import java.util.*;
410 411
import java.util.function.*;
411 412
import java.util.logging.*;
413

  
414
import com.goldencode.p2j.jmx.FwdJMX;
415
import com.goldencode.p2j.jmx.NanoTimer;
412 416
import com.goldencode.p2j.persist.dirty.*;
413 417
import com.goldencode.p2j.persist.event.*;
414 418
import com.goldencode.p2j.persist.lock.*;
......
489 493
{
490 494
   /** Logger */
491 495
   private static final Logger LOG = LogHelper.getLogger(RandomAccessQuery.class.getName());
496

  
497
   /** Instrumentation for <code>RandomAccessQuery</code> fast-find cache hits. */
498
   private static final NanoTimer FF_CACHE_HIT = 
499
            NanoTimer.getInstance(FwdJMX.TimeStat.OrmFFCacheHit);
500

  
501
   /** Instrumentation for <code>RandomAccessQuery</code> direct access technique over unique index. */
502
   private static final NanoTimer FF_CACHE_HIT_NO_RECORD = 
503
            NanoTimer.getInstance(FwdJMX.TimeStat.OrmFFCacheHitNoRecord);
504
   
505
   /** Instrumentation for <code>RandomAccessQuery</code> direct access technique over recid. */
506
   private static final NanoTimer FIND_DIRECT_ACCESS_PK = 
507
            NanoTimer.getInstance(FwdJMX.TimeStat.OrmFindDirectAccessPk);
508

  
509
   /** Instrumentation for <code>RandomAccessQuery</code> direct access technique over unique index. */
510
   private static final NanoTimer FIND_DIRECT_ACCESS = 
511
            NanoTimer.getInstance(FwdJMX.TimeStat.OrmFindDirectAccess);
512

  
513
   /** Instrumentation for <code>RandomAccessQuery.executeImpl</code>. */
514
   private static final NanoTimer FIND_EXECUTE = 
515
            NanoTimer.getInstance(FwdJMX.TimeStat.OrmFindExecute);   
492 516
   
493 517
   /** Record buffer which backs this query */
494 518
   private RecordBuffer buffer;
......
3444 3468
         RecordIdentifier<String> cacheResult = null;
3445 3469
         FastFindCache.Key key = null;
3446 3470
         FQLPreprocessor fqlPreproc = helper.getFQLPreprocessor();
3471
         DmoMeta meta = buffer.getDmoInfo();
3447 3472
         
3448 3473
         // attempt to get the result from fast-find cache, avoiding the expensive database trip.
3449 3474
         // This is not possible and the cache lookup is skipped when:
......
3451 3476
         //  - the query has a join
3452 3477
         //  - the query has an embedded CAN-FIND
3453 3478
         //  - the query has a client-side where expression
3454
         if (ffCache != null &&
3455
             (activeBundleKey == FIRST || activeBundleKey == LAST || activeBundleKey == UNIQUE) &&
3456
             join == null && getExternalBuffers() == null)
3479
         if (ffCache != null && isSimpleQuery())
3457 3480
         {
3458 3481
            BitSet properties = fqlPreproc.getQueryProperties();
3459 3482
            key = FastFindCache.createKey(buffer, index, where, properties, activeBundleKey, values);
......
3464 3487
               if (cacheResult == FastFindCache.NO_RECORD)
3465 3488
               {
3466 3489
                  // record will not be found in database
3490
                  FF_CACHE_HIT_NO_RECORD.timer(() -> { });
3467 3491
                  return null;
3468 3492
               }
3469 3493
               else
......
3483 3507
                        // were acquiring the lock; it is safe to use:
3484 3508
                        try
3485 3509
                        {
3486
                           return session.get(cacheResult);
3510
                           Record[] r = new Record[1];
3511
                           RecordIdentifier<String> internalCacheResult = cacheResult;
3512
                           FF_CACHE_HIT.timer(() -> r[0] = session.get(internalCacheResult));
3513
                           return r[0];
3487 3514
                        }
3488
                        catch (PersistenceException e)
3515
                        catch (RuntimeException e)
3489 3516
                        {
3490
                           if (LOG.isLoggable(Level.WARNING))
3491
                           {
3492
                              LOG.log(Level.WARNING, 
3493
                                      "Failed to get fast-find cached value for " + cacheResult,
3494
                                      e);
3517
                           if (e.getCause() instanceof PersistenceException)
3518
                           {
3519
                              if (LOG.isLoggable(Level.WARNING))
3520
                              {
3521
                                 LOG.log(Level.WARNING, 
3522
                                         "Failed to get fast-find cached value for " + cacheResult,
3523
                                         e.getCause());
3524
                              }
3525
                           }
3526
                           else
3527
                           {
3528
                              throw e;
3495 3529
                           }
3496 3530
                        }
3497 3531
                     }
......
3508 3542
            }
3509 3543
         }
3510 3544
         
3511
         assert (cacheResult == null);
3512
         Record record = executeImpl(values, lockType, unique, updateLock);
3545
         Record record = null;
3546
         
3547
         // use direct-access after ffCache failed
3548
         Optional<Record> directAccessResult = executeDirectAccess(values);
3549
         if (directAccessResult.isPresent())
3550
         {
3551
            record = directAccessResult.get();
3552
         }
3553
         else
3554
         {
3555
            Record[] r = new Record[1];
3556
            FIND_EXECUTE.timer(() -> r[0] = executeImpl(values, lockType, unique, updateLock));
3557
            record = r[0];
3558
         }
3513 3559
         
3514 3560
         if (ffCache != null &&
3515 3561
             cacheResult == null &&
......
4735 4781
   }
4736 4782
   
4737 4783
   /**
4784
    * Core method of attempting to retrieve the record for this query using direct access.
4785
    * This does all the prerequisites checks and is safely implemented such that a failed direct
4786
    * access allows easy recovery. 
4787
    * <p>
4788
    * A simple query (no join, external buffers and single result) over the temporary database 
4789
    * can be resolved using a direct access driver (currently supported by H2). This avoids generating
4790
    * an FQL/SQL and simply uses the internal H2 structures (tables, indexes, etc.) to find the
4791
    * searched record.
4792
    * <p>
4793
    * Currently, this can be used only if the H2 row structure is very similar to the FWD record 
4794
    * structure (to allow hydration) and the searched row can be uniquely identified: using recid
4795
    * or an unique index.
4796
    * 
4797
    * @param    values
4798
    *           The resolved substitution parameters to be used by the query.
4799
    *           
4800
    * @return   An optional depending on the success of this attempt. If a direct access exception 
4801
    *           occurred, allow fallback by returning an empty optional. If a record could be retrieved,
4802
    *           get it through the optional.
4803
    */
4804
   private Optional<Record> executeDirectAccess(Object[] values)
4805
   {
4806
      try
4807
      {
4808
         // reduce expenses; don't create a session just to check direct access
4809
         Session currentSession = buffer.getSession(false);
4810
         DmoMeta meta = buffer.getDmoInfo();
4811
         FQLPreprocessor fqlPreproc = helper.getFQLPreprocessor();
4812
         if (currentSession != null && 
4813
             isSimpleQuery() && 
4814
             buffer.isTemporary() && // prerequisites
4815
             fqlPreproc.isUniqueFind(true) && // this is the core condition right now
4816
             DirectAccessHelper.hasDirectAccess(currentSession) && // only if we have direct access to H2 
4817
             !meta.hasComputedColumns(false)) // only if all H2 columns represent the DMO fields
4818
         {            
4819
            Record[] r = new Record[1];
4820
            if (fqlPreproc.isFindByRowid())
4821
            {
4822
               long recid = fqlPreproc.getFindByRowid(values);
4823
               FIND_DIRECT_ACCESS_PK.timer(() ->
4824
                  r[0] = DirectAccessHelper.findByRowid(currentSession, meta, recid)
4825
               );
4826
            }
4827
            else if (fqlPreproc.isUniqueFind(false))
4828
            {
4829
               // make a copy to add the custom multiplex
4830
               UniqueIndexLookup lookup = new UniqueIndexLookup(fqlPreproc.getUniqueIndexLookup()); 
4831
               if (!lookup.fill(values))
4832
               {
4833
                  throw new DirectAccessException("Invalid fill operation");
4834
               }
4835
               lookup.addMapping(TemporaryBuffer.MULTIPLEX_FIELD_NAME, buffer.getMultiplexID());
4836
               FIND_DIRECT_ACCESS.timer(() -> 
4837
                  r[0] = DirectAccessHelper.findByUniqueIndex(currentSession, meta, lookup)
4838
               );
4839
            }
4840
            else
4841
            {
4842
               throw new DirectAccessException("Invalid state for direct access");
4843
               // not a valid state
4844
            }
4845
            
4846
            // the record can be null; make sure we let the caller know that we succeeded
4847
            return Optional.of(r[0]);
4848
         }
4849
      }
4850
      catch (Exception e)
4851
      {
4852
         if (e instanceof DirectAccessException || e.getCause() instanceof DirectAccessException)
4853
         {
4854
            return Optional.empty();
4855
         }
4856
         else
4857
         {
4858
            throw new RuntimeException(e);
4859
         }
4860
      }
4861

  
4862
      // don't use exceptions if we fail here; if we do, there will be a lot of exception being thrown 
4863
      // it is faster to just let the caller know that we failed or not using DirectAccessResult
4864
      return Optional.empty();
4865
   }
4866
   
4867
   /**
4738 4868
    * Process the results of a dirty index check and return the most
4739 4869
    * appropriate result for the query.  This method may acquire a record lock
4740 4870
    * for the selected result.
......
4841 4971
   }
4842 4972
   
4843 4973
   /**
4974
    * Check if this is a "simple" query. The term simple is vague. It refers to queries which can be
4975
    * easier resolved through fast-find cache or direct access. "simple" means no join, no external
4976
    * buffer and no dependent bundle key (FIRST, LAST and UNIQUE do not depend on a previous
4977
    * sorted query).
4978
    * 
4979
    * @return   {@code true} if this query can be used by fast-find or direct access.
4980
    */
4981
   private boolean isSimpleQuery()
4982
   {
4983
      return (activeBundleKey == FIRST || activeBundleKey == LAST || activeBundleKey == UNIQUE) &&
4984
              join == null && getExternalBuffers() == null;
4985
   }
4986
   
4987
   /**
4844 4988
    * Log debug details about the inputs and results of a query.
4845 4989
    * 
4846 4990
    * @param   hql
new/src/com/goldencode/p2j/persist/TempTableHelper.java 2023-02-10 14:18:57 +0000
54 54
**     OM  20221027          H2 dialect requires that the index names to be truly unique.
55 55
**     SVL 20230110          P2JIndex.components() provides direct access to the components array in order to
56 56
**                           improve performance.
57
**     AL2 20230210          Mark the computed columns as INVISIBLE. This works as a hint for the H2 engine.
57 58
*/
58 59

  
59 60
/*
......
591 592
         {
592 593
            String sqlType = dialect.getSqlMappedType(fieldMeta._fwdType, normalizedScale);
593 594
            String ccFormula = dialect.getComputedColumnFormula(fieldMeta.column, !fieldMeta.caseSensitive);
595
            String invisible = dialect.getInvisibleSpecificator();
594 596
            sb.append(tab)
595 597
              .append(dialect.getComputedColumnPrefix(fieldMeta.caseSensitive)).append(fieldMeta.column)
596
              .append(" ").append(sqlType).append(" as ").append(ccFormula)
598
              .append(" ").append(sqlType)
599
              .append(invisible != null ? " " + invisible : "")
600
              .append(" as ").append(ccFormula)
597 601
              .append(",\n");
598 602
         }
599 603
         
......
601 605
         // in case it is lost 
602 606
         if (fieldMeta._isDatetimeTz)
603 607
         {
608
            String invisible = dialect.getInvisibleSpecificator();
604 609
            sb.append(tab)
605 610
              .append(fieldMeta.column).append(DDLGeneratorWorker.DTZ_OFFSET).append(" ")
606 611
              .append(dialect.getSqlMappedType("integer", 0))
612
              .append(invisible != null ? " " + invisible : "")
607 613
              .append(mandatoryColumn ? " not null,\n" : ",\n");
608 614
         }
609 615
      }
new/src/com/goldencode/p2j/persist/UniqueIndexLookup.java 2023-02-10 14:32:31 +0000
1
/*
2
** Module   : UniqueIndexLookup.java
3
** Abstract : Container for properties and values used when doing a unique index look-up.
4
**
5
** Copyright (c) 2023, Golden Code Development Corporation.
6
**
7
** -#- -I- --Date-- -----------------------------------Description-----------------------------------
8
** 001 AL2 20230210 Created initial version.
9
*/
10

  
11
/*
12
** This program is free software: you can redistribute it and/or modify
13
** it under the terms of the GNU Affero General Public License as
14
** published by the Free Software Foundation, either version 3 of the
15
** License, or (at your option) any later version.
16
**
17
** This program is distributed in the hope that it will be useful,
18
** but WITHOUT ANY WARRANTY; without even the implied warranty of
19
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
** GNU Affero General Public License for more details.
21
**
22
** You may find a copy of the GNU Affero GPL version 3 at the following
23
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
24
** 
25
** Additional terms under GNU Affero GPL version 3 section 7:
26
** 
27
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
28
**   terms apply to the works covered under the License.  These additional terms
29
**   are non-permissive additional terms allowed under Section 7 of the GNU
30
**   Affero GPL version 3 and may not be removed by you.
31
** 
32
**   0. Attribution Requirement.
33
** 
34
**     You must preserve all legal notices or author attributions in the covered
35
**     work or Appropriate Legal Notices displayed by works containing the covered
36
**     work.  You may not remove from the covered work any author or developer
37
**     credit already included within the covered work.
38
** 
39
**   1. No License To Use Trademarks.
40
** 
41
**     This license does not grant any license or rights to use the trademarks
42
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
43
**     of Golden Code Development Corporation. You are not authorized to use the
44
**     name Golden Code, FWD, or the names of any author or contributor, for
45
**     publicity purposes without written authorization.
46
** 
47
**   2. No Misrepresentation of Affiliation.
48
** 
49
**     You may not represent yourself as Golden Code Development Corporation or FWD.
50
** 
51
**     You may not represent yourself for publicity purposes as associated with
52
**     Golden Code Development Corporation, FWD, or any author or contributor to
53
**     the covered work, without written authorization.
54
** 
55
**   3. No Misrepresentation of Source or Origin.
56
** 
57
**     You may not represent the covered work as solely your work.  All modified
58
**     versions of the covered work must be marked in a reasonable way to make it
59
**     clear that the modified work is not originating from Golden Code Development
60
**     Corporation or FWD.  All modified versions must contain the notices of
61
**     attribution required in this license.
62
*/
63

  
64
package com.goldencode.p2j.persist;
65

  
66
import java.util.ArrayList;
67
import java.util.List;
68

  
69
/**
70
 * Utility class which stores the properties and values of a look-up which is done 
71
 * over an unique index. 
72
 */
73
public class UniqueIndexLookup
74
{
75
   /** The properties of the unique index, not necessarily in order */
76
   private List<String> properties;
77
   
78
   /** 
79
    * The values used for look-up; the order matched the order of the properties
80
    * A null value means a value that is provided latter (by substitution parameters).
81
    */
82
   private List<Object> values;
83

  
84
   /**
85
    * Basic constructor
86
    */
87
   public UniqueIndexLookup()
88
   {
89
      properties = new ArrayList<>();
90
      values = new ArrayList<>();
91
   }
92
   
93
   /**
94
    * Copy constructor
95
    * 
96
    * @param  prototype
97
    *         The prototype to be used when building this object.
98
    */
99
   public UniqueIndexLookup(UniqueIndexLookup prototype)
100
   {
101
      this.properties = new ArrayList<>(prototype.properties);
102
      // deep copy is not needed; values are immutable
103
      this.values = new ArrayList<>(prototype.values);
104
   }
105
   
106
   /**
107
    * Fill the {@code null} values from the values array with the provided fill values.
108
    * This is used to lazily add the substitution parameters when needed. 
109
    * 
110
    * @param   fillValues
111
    *          The values to be used when filling the parameters array.
112
    *          
113
    * @return  {@code true} if the filling succeeded. If this returns {@code false}, the 
114
    *          object reaches a stale state, so avoid using it.
115
    */
116
   public boolean fill(Object[] fillValues)
117
   {
118
      int from = 0;
119
      for (int i = 0; i < this.values.size(); i++)
120
      {
121
         if (this.values.get(i) == null)
122
         {
123
            if (fillValues == null || fillValues.length <= from)
124
            {
125
               return false;
126
            }
127
            this.values.set(i, fillValues[from++]);
128
         }
129
      }
130
      if (fillValues == null)
131
      {
132
         return from == 0;
133
      }
134
      return from == fillValues.length;
135
   }
136

  
137
   /** 
138
    * Add a new property-value mapping to this look-up.
139
    * 
140
    * @param   property
141
    *          The property from an unique index.
142
    * @param   value
143
    *          The value to be used when doing look-up.
144
    */
145
   public void addMapping(String property, Object value)
146
   {
147
      properties.add(property);
148
      values.add(value);
149
   }
150

  
151
   /**
152
    * Retrieve the properties as an array.
153
    * 
154
    * @return   The array of properties.
155
    */
156
   public String[] getProperties()
157
   {
158
      return properties.toArray(new String[0]);
159
   }
160
   
161
   /**
162
    * Retrieve the values as an array.
163
    * 
164
    * @return   The array of values.
165
    */
166
   public Object[] getValues()
167
   {
168
      return values.toArray();
169
   }
170
   
171
}
new/src/com/goldencode/p2j/persist/dialect/Dialect.java 2023-02-10 14:22:34 +0000
95 95
**     SVL 20230110          P2JIndex.components() provides direct access to the components array in order to
96 96
**                           improve performance.
97 97
**     ECF 20230127          Added resolveBooleanConstant method.
98
**     AL2 20230210          Added getInvisibleSpecificator.
98 99
*/
99 100

  
100 101
/*
......
1061 1062
   }
1062 1063
   
1063 1064
   /**
1065
    * Return a string which marks that a column is computed, so it should be marked as invisible
1066
    * to allow implicit skipping of it when using wildcard selection (*) or direct access.
1067
    * 
1068
    * @return   An SQL string specificator for invisible columns. {@code null} means that no such 
1069
    *           specificator is supported. 
1070
    */
1071
   public String getInvisibleSpecificator()
1072
   {
1073
      return null;
1074
   }
1075
   
1076
   /**
1064 1077
    * Prepare the given metadata query parameter.
1065 1078
    * 
1066 1079
    * @param   param
new/src/com/goldencode/p2j/persist/dialect/P2JH2Dialect.java 2023-02-10 14:22:21 +0000
106 106
**     OM  20221026          Excluded table name from index name by default. It will be added as needed by
107 107
**                           dialects.
108 108
**     RAA 20230109          Overrided methods getSequenceSetValString() and getSequenceCurrValString().
109
**     AL2 20230210          Added getInvisibleSpecificator.
109 110
*/
110 111

  
111 112
/*
......
1017 1018
   }
1018 1019

  
1019 1020
   /**
1021
    * Return a string which marks that a column is computed, so it should be marked as invisible
1022
    * to allow implicit skipping of it when using wildcard selection (*) or direct access.
1023
    * 
1024
    * @return   An SQL string specificator for invisible columns. {@code null} means that no such 
1025
    *           specificator is supported. 
1026
    */
1027
   public String getInvisibleSpecificator()
1028
   {
1029
      return "invisible";
1030
   }
1031

  
1032
   /**
1020 1033
    * Prepare the given metadata query parameter by making it uppercase.
1021 1034
    * 
1022 1035
    * @param   param
new/src/com/goldencode/p2j/persist/hql/DataTypeHelper.java 2023-02-10 14:22:01 +0000
37 37
** 022 IAS 20201123          Added ARRAY type
38 38
**     IAS 20220926          Process extra parentheses in the expressionType() method.
39 39
**     OM  20221103          New class names for FQLPreprocessor, FQLExpression, FQLBundle, and FQLCache.
40
**     AL2 20230210          Allow permissive look-up buffer (without exception of fail).
40 41
*/
41 42

  
42 43
/*
......
433 434
                                           FQLExpression fql)
434 435
   {
435 436
      String aliasName = alias != null ? alias.getText() : null;
436
      RecordBuffer buffer = bufferMap.get(aliasName);
437
      
438
      if (buffer == null)
437
      RecordBuffer rb = lookupBuffer(aliasName, bufferMap, fql);
438
      if (rb == null)
439 439
      {
440
         throw new IllegalArgumentException(
441
            "Unrecognized buffer at column " +
440
         throw new IllegalArgumentException("Unrecognized buffer at column " +
442 441
            (alias != null ? alias.getColumn() : "<unknown>") +
443 442
            " in where clause:  '" +
444 443
            aliasName +
445 444
            "' [" +
446 445
            (fql != null ? fql.toString() : "<unavailable>") +
447
            "]");
446
            "]"
447
         );
448
      };
449
      return rb;
450
   }
451
   
452
   /**
453
    * Attempt to look up a record buffer by alias from the given map. The DMO alias is extracted
454
    * from the {@code alias} AST. If there is no matching entry in the buffer map, an exception
455
    * is thrown.
456
    * <p>
457
    * The returned buffer is initialized by side effect.
458
    * 
459
    * @param   aliasName
460
    *          Alias for DMO. If {@code null}, a {@code null} lookup key is used.
461
    * @param   bufferMap
462
    *          A map of DMO aliases to record buffers. If no alias is provided (e.g., for an
463
    *          unqualified property reference, the appropriate buffer is expected to be mapped
464
    *          to the {@code null} key.
465
    * @param   fql
466
    *          Optional FQL expression from which the alias is parsed.
467
    * 
468
    * @return  A record buffer matching the given alias.
469
    */
470
   public static RecordBuffer lookupBuffer(String aliasName,
471
                                            Map<String, RecordBuffer> bufferMap,
472
                                            FQLExpression fql)
473
   {
474
      RecordBuffer buffer = bufferMap.get(aliasName);
475
      
476
      if (buffer == null)
477
      {
478
         return null;
448 479
      }
449 480
      
450 481
      buffer.initialize();
new/src/com/goldencode/p2j/persist/orm/DirectAccessException.java 2023-02-10 14:34:13 +0000
1
/*
2
** Module   : DirectAccessException.java
3
** Abstract : Exception related to the direct access of internal database structures technique.
4
**
5
** Copyright (c) 2023, Golden Code Development Corporation.
6
**
7
** -#- -I- --Date-- -----------------------------------Description-----------------------------------
8
** 001 AL2 20230210 Created initial version.
9
*/
10

  
11
/*
12
** This program is free software: you can redistribute it and/or modify
13
** it under the terms of the GNU Affero General Public License as
14
** published by the Free Software Foundation, either version 3 of the
15
** License, or (at your option) any later version.
16
**
17
** This program is distributed in the hope that it will be useful,
18
** but WITHOUT ANY WARRANTY; without even the implied warranty of
19
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
** GNU Affero General Public License for more details.
21
**
22
** You may find a copy of the GNU Affero GPL version 3 at the following
23
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
24
** 
25
** Additional terms under GNU Affero GPL version 3 section 7:
26
** 
27
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
28
**   terms apply to the works covered under the License.  These additional terms
29
**   are non-permissive additional terms allowed under Section 7 of the GNU
30
**   Affero GPL version 3 and may not be removed by you.
31
** 
32
**   0. Attribution Requirement.
33
** 
34
**     You must preserve all legal notices or author attributions in the covered
35
**     work or Appropriate Legal Notices displayed by works containing the covered
36
**     work.  You may not remove from the covered work any author or developer
37
**     credit already included within the covered work.
38
** 
39
**   1. No License To Use Trademarks.
40
** 
41
**     This license does not grant any license or rights to use the trademarks
42
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
43
**     of Golden Code Development Corporation. You are not authorized to use the
44
**     name Golden Code, FWD, or the names of any author or contributor, for
45
**     publicity purposes without written authorization.
46
** 
47
**   2. No Misrepresentation of Affiliation.
48
** 
49
**     You may not represent yourself as Golden Code Development Corporation or FWD.
50
** 
51
**     You may not represent yourself for publicity purposes as associated with
52
**     Golden Code Development Corporation, FWD, or any author or contributor to
53
**     the covered work, without written authorization.
54
** 
55
**   3. No Misrepresentation of Source or Origin.
56
** 
57
**     You may not represent the covered work as solely your work.  All modified
58
**     versions of the covered work must be marked in a reasonable way to make it
59
**     clear that the modified work is not originating from Golden Code Development
60
**     Corporation or FWD.  All modified versions must contain the notices of
61
**     attribution required in this license.
62
*/
63

  
64

  
65
package com.goldencode.p2j.persist.orm;
66

  
67
/**
68
 * Exception used when anything goes wrong in the direct access attempts. This is used to
69
 * do recovery in case direct access doesn't work. As direct access attempts are read-only,
70
 * throwing such exception doesn't imply stale states. Therefore, ensure that a fallback 
71
 * plan is used if such exception occurs.
72
 */
73
public class DirectAccessException
74
extends Exception
75
{
76
   /**
77
    * Basic constructor
78
    * 
79
    * @param   msg
80
    *          Exception message.
81
    */
82
   public DirectAccessException(String msg)
83
   {
84
      super(msg);
85
   }
86
   
87
   /**
88
    * Basic constructor
89
    * 
90
    * @param   msg
91
    *          Exception message.
92
    * @param   cause 
93
    *          Exception cause.
94
    */
95
   public DirectAccessException(String msg, Exception cause)
96
   {
97
      super(msg, cause);
98
   }
99

  
100
}
new/src/com/goldencode/p2j/persist/orm/DirectAccessHelper.java 2023-02-10 14:42:16 +0000
1
/*
2
** Module   : DirectAccessHelper.java
3
** Abstract : Middleware between FWD persistence layer and underlying database with direct access interface.
4
**
5
** Copyright (c) 2023, Golden Code Development Corporation.
6
**
7
** -#- -I- --Date-- -----------------------------------Description-----------------------------------
8
** 001 AL2 20230210 Created initial version.
9
*/
10

  
11
/*
12
** This program is free software: you can redistribute it and/or modify
13
** it under the terms of the GNU Affero General Public License as
14
** published by the Free Software Foundation, either version 3 of the
15
** License, or (at your option) any later version.
16
**
17
** This program is distributed in the hope that it will be useful,
18
** but WITHOUT ANY WARRANTY; without even the implied warranty of
19
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
** GNU Affero General Public License for more details.
21
**
22
** You may find a copy of the GNU Affero GPL version 3 at the following
23
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
24
** 
25
** Additional terms under GNU Affero GPL version 3 section 7:
26
** 
27
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
28
**   terms apply to the works covered under the License.  These additional terms
29
**   are non-permissive additional terms allowed under Section 7 of the GNU
30
**   Affero GPL version 3 and may not be removed by you.
31
** 
32
**   0. Attribution Requirement.
33
** 
34
**     You must preserve all legal notices or author attributions in the covered
35
**     work or Appropriate Legal Notices displayed by works containing the covered
36
**     work.  You may not remove from the covered work any author or developer
37
**     credit already included within the covered work.
38
** 
39
**   1. No License To Use Trademarks.
40
** 
41
**     This license does not grant any license or rights to use the trademarks
42
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
43
**     of Golden Code Development Corporation. You are not authorized to use the
44
**     name Golden Code, FWD, or the names of any author or contributor, for
45
**     publicity purposes without written authorization.
46
** 
47
**   2. No Misrepresentation of Affiliation.
48
** 
49
**     You may not represent yourself as Golden Code Development Corporation or FWD.
50
** 
51
**     You may not represent yourself for publicity purposes as associated with
52
**     Golden Code Development Corporation, FWD, or any author or contributor to
53
**     the covered work, without written authorization.
54
** 
55
**   3. No Misrepresentation of Source or Origin.
56
** 
57
**     You may not represent the covered work as solely your work.  All modified
58
**     versions of the covered work must be marked in a reasonable way to make it
59
**     clear that the modified work is not originating from Golden Code Development
60
**     Corporation or FWD.  All modified versions must contain the notices of
61
**     attribution required in this license.
62
*/
63

  
64
package com.goldencode.p2j.persist.orm;
65

  
66
import java.sql.Connection;
67
import java.sql.ResultSet;
68
import java.sql.SQLException;
69

  
70
import org.h2.embedded.DirectAccessDriver;
71
import org.h2.jdbc.JdbcConnection;
72

  
73
import com.goldencode.p2j.persist.PersistenceException;
74
import com.goldencode.p2j.persist.Record;
75
import com.goldencode.p2j.persist.UniqueIndexLookup;
76
import com.goldencode.p2j.util.*;
77

  
78
/**
79
 * Common point where all the direct access interface is used inside FWD. Any attempt
80
 * to use a database driver internal structures should be done through this gateway.
81
 */
82
public class DirectAccessHelper
83
{
84
   /**
85
    * Direct access attempt on database structures to retrieve a single record based on recid.
86
    * 
87
    * @param   session 
88
    *          The session with a database with direct access interface.
89
    * @param   meta
90
    *          The meta of the table to be queried.
91
    * @param   recid
92
    *          The value of the recid for the record to be retrieved.
93
    *          
94
    * @return  A hydrated record directly from the underlying direct access interface.
95
    */
96
   public static Record findByRowid(Session session, DmoMeta meta, long recid)
97
   throws DirectAccessException
98
   {
99
      try
100
      {
101
         JdbcConnection conn = getH2Connection(session);
102
         DirectAccessDriver driver = conn.getDirectAccessDriver();
103
         String tableName = meta.getSqlTableName();
104
         ResultSet rs = driver.getRow(tableName, recid);
105
         return hydrateFromResultSet(session, meta, rs);
106
      }
107
      catch (SQLException | PersistenceException e)
108
      {
109
         throw new DirectAccessException("Error direct accessing for finding record by rowid", e);
110
      }
111
   }
112

  
113
   /**
114
    * Direct access attempt on database structures to retrieve a single record based on a 
115
    * unique index.
116
    * 
117
    * @param   session 
118
    *          The session with a database with direct access interface.
119
    * @param   meta
120
    *          The meta of the table to be queried.
121
    * @param   lookup
122
    *          Specific object which contains low-level information about the properties 
123
    *          and values needed for unique index lookup. 
124
    *          
125
    * @return  A hydrated record directly from the underlying direct access interface.
126
    */
127
   public static Record findByUniqueIndex(Session session, DmoMeta meta, UniqueIndexLookup lookup)
128
   throws DirectAccessException
129
   {
130
      try
131
      {
132
         JdbcConnection conn = getH2Connection(session);
133
         DirectAccessDriver driver = conn.getDirectAccessDriver();
134
         String tableName = meta.getSqlTableName();
135
         Object[] values = lookup.getValues();
136
         for (int i = 0; i < values.length; i++)
137
         {
138
            values[i] = normalizeValue(values[i]);
139
         }
140
         ResultSet rs = driver.getUniqueRow(tableName, lookup.getProperties(), values);
141
         return hydrateFromResultSet(session, meta, rs);
142
      }
143
      catch (SQLException | PersistenceException e)
144
      {
145
         throw new DirectAccessException("Error direct accessing for finding record by unique index", e);
146
      }
147
   }
148

  
149
   /**
150
    * Check if the provided session allows a direct access interface.
151
    * 
152
    * @param   session 
153
    *          The session with a database with direct access interface.
154
    *          
155
    * @return  {@code true} if this session allows direct access.
156
    */
157
   public static boolean hasDirectAccess(Session session)
158
   {
159
      return getH2Connection(session) != null;
160
   }
161

  
162
   /**
163
    * Normalize the FWD specific data types into Java native types. This is needed
164
    * in order to interface with the underlying database through direct access.
165
    * 
166
    * @param   arg
167
    *          The object to be "normalized" (converted to Java native type)
168
    *          
169
    * @return  A Java native type that can be used by the direct access interface.
170
    */
171
   private static Object normalizeValue(Object arg)
172
   throws DirectAccessException
173
   {
174
      if (arg == null)
175
      {
176
         return null;
177
      }
178
      if (arg instanceof Integer || 
179
          arg instanceof Long    || 
180
          arg instanceof Boolean || 
181
          arg instanceof Double  ||
182
          arg instanceof String)
183
      {
184
         // already normalized
185
         return arg;
186
      }
187
      if (arg instanceof integer)
188
      {
189
         return ((integer) arg).toJavaIntegerType();
190
      }
191
      if (arg instanceof int64)
192
      {
193
         return ((int64) arg).toJavaLongType();
194
      }
195
      if (arg instanceof logical)
196
      {
197
         return ((logical) arg).toJavaType();
198
      }
199
      if (arg instanceof handle)
200
      {
201
         return ((handle) arg).getResourceId();
202
      }
203
      if (arg instanceof character)
204
      {
205
         return ((character) arg).toJavaType();
206
      }
207
      if (arg instanceof decimal)
208
      {
209
         return ((decimal) arg).toJavaType();
210
      }
211
      
212
      throw new DirectAccessException("Can't normalize FWD value to use it with direct access.");
213
   }
214
   
215
   /**
216
    * Core method of hydrating a record from the retrieved result set. Usually, the result-set
217
    * is a dummy container of row structures from the underlying database. Make sure these
218
    * are converted to FWD structures by hydrating.
219
    * 
220
    * @param   session 
221
    *          The session with a database with direct access interface.
222
    * @param   meta
223
    *          The meta of the table to be queried.
224
    * @param   rs
225
    *          The result-set returned by the direct access interface.
226
    *          
227
    * @return  A hydrated record directly from the underlying direct access interface.
228
    */
229
   private static Record hydrateFromResultSet(Session session, DmoMeta meta, ResultSet rs)
230
   throws PersistenceException, 
231
          SQLException
232
   {
233
      RowStructure rowStructure = new RowStructure(meta.dmoImplInterface, meta.propsByName.size());
234
      if (rs.next())
235
      {
236
         return (Record) SQLQuery.hydrateRecord(rs, rowStructure, 1, session);
237
      }
238
      else
239
      {
240
         return null;
241
      }
242
   }
243
   
244
   /**
245
    * Retrieve the H2 connection straight from the provided session. This returns {@code null}
246
    * if the H2 connection doesn't allows direct access interface of if this is not a H2 connection.
247
    * This should be refactored maybe to be more generic (not necessarily that close related to H2)
248
    * 
249
    * @param   session 
250
    *          The session with a database with direct access interface.
251
    *          
252
    * @return  A H2 specific connection.
253
    */
254
   private static JdbcConnection getH2Connection(Session session)
255
   {
256
      if (session == null)
257
      {
258
         return null;
259
      }
260
      
261
      Connection conn;
262
      try
263
      {
264
         conn = session.getConnection();
265
      }
266
      catch (PersistenceException e)
267
      {
268
         return null;
269
      }
270
      
271
      if (TempTableDataSourceProvider.isProxy(conn))
272
      {
273
         conn = TempTableDataSourceProvider.unwrapProxy(conn);
274
      }
275
      
276
      if (conn != null && 
277
          conn instanceof JdbcConnection && 
278
          ((JdbcConnection) conn).getDirectAccessDriver() != null)
279
      {
280
         return (JdbcConnection) conn;
281
      }
282
      
283
      return null;
284
   }
285

  
286
}
new/src/com/goldencode/p2j/persist/orm/DmoMeta.java 2023-02-10 14:23:03 +0000
49 49
**     SVL 20230113 Improved performance by replacing some "for-each" loops with indexed "for" loops.
50 50
**     HC  20230118 Eliminated some of the uses of String.toUpperCase and/or String.toLowerCase
51 51
**                  for performance.
52
**     AL2 20230120 Compute if the underlying DMO requires extra computed columns.
52 53
**     CA  20221114 Added API to get the denormalized properties for a DMO extent property name.
53 54
**     ECF 20221207 Changed heuristic to extract schema name from full DMO interface name.
54 55
*/
......
282 283
   /** Map of property names to extents for DMO array properties */
283 284
   private Map<String, Integer> extentMap = null;
284 285
   
286
   /** Check if this DMO is going to have computed columns in the underlying database representation */
287
   private Boolean[] hasComputedColumns = null;
288
   
285 289
   /** The map used in type-widening operations. */
286 290
   private static final Map<Class<? extends BaseDataType>, Class<? extends BaseDataType>> toWider = 
287 291
         new IdentityHashMap<>();
......
1595 1599
      return Category.VST.name().equals(category) && systemTable.fileName.equalsIgnoreCase(legacyTable);
1596 1600
   }
1597 1601
   
1602
   
1603
   /**
1604
    * Check if this DMO has computed columns in the underlying database representation. This is 
1605
    * used mostly for temporary tables and may not be completely compatible with persistent tables.
1606
    * 
1607
    * @param    includeInvisible 
1608
    *           {@code true} to include the columns which are defined as invisible in the underlying 
1609
    *           database. Invisible columns are the ones which are not implicitly retrieved using 
1610
    *           SELECT * or direct access.
1611
    * 
1612
    * @return   {@code true} if we also have other columns that the legacy ones in the database.
1613
    */
1614
   public boolean hasComputedColumns(boolean includeInvisible)
1615
   {
1616
      if (hasComputedColumns == null)
1617
      {
1618
         hasComputedColumns = new Boolean[2];
1619
      }
1620

  
1621
      int position = includeInvisible ? 0 : 1;
1622
      if (hasComputedColumns[position] == null)
1623
      {
1624
         hasComputedColumns[position] = false;
1625
         
1626
         Set<Map.Entry<String, Property>> fieldEntries = this.propsByName.entrySet();
1627
 
1628
         Set<Property> indexedProperties = new HashSet<>();
1629
         Iterator<P2JIndex> databaseIndexes = this.getDatabaseIndexes();
1630
       
1631
         while (databaseIndexes.hasNext())
1632
         {
1633
            P2JIndex index = databaseIndexes.next();
1634
            ArrayList<P2JIndexComponent> components = index.components();
1635
            for (int i = 0; i < components.size(); i++)
1636
            {
1637
               P2JIndexComponent comp = components.get(i);            
1638
               Property property = this.getFieldInfo(comp.getPropertyName());
1639
               indexedProperties.add(property);
1640
            }
1641
         }
1642

  
1643
         // temp-tables have invisible columns
1644
         boolean relaxed = tempTable && !includeInvisible;
1645
         for (Map.Entry<String, Property> field : fieldEntries)
1646
         {
1647
            Property property = field.getValue();
1648
            
1649
            if (!relaxed && indexedProperties.contains(property) && property._isCharacter)
1650
            {
1651
               hasComputedColumns[position] = true;
1652
               break;
1653
            }
1654

  
1655
            if (property.extent > 0)
1656
            {
1657
               hasComputedColumns[position] = true;
1658
               break;
1659
            }
1660
            
1661
            if (!relaxed && property._isDatetimeTz)
1662
            {
1663
               hasComputedColumns[position] = true;
1664
               break;
1665
            }
1666
         }
1667
      }
1668
      return hasComputedColumns[position];
1669
   }
1670
   
1598 1671
   /**
1599 1672
    * Get the map of DMO property names to {@code P2JField} objects describing the corresponding fields,
1600 1673
    * creating it first, if necessary.
new/src/com/goldencode/p2j/persist/orm/TempTableDataSourceProvider.java 2023-02-10 14:26:23 +0000
15 15
**     CA  20221011 Decreased prepared statement cache to 2048.  The PreparedStatements are not cleaned when
16 16
**                  a temp-table is dropped, and this will pin in memory the entire temp-table (with rows,
17 17
**                  indexes, etc) at H2 level.
18
** 003 AL2 20230210 Major refactor. Proxied connection is no longer an annonymous class. Make it named
19
**                  to identify if the connection is proxied or not.
18 20
*/
19 21

  
20 22
/*
......
96 98
class TempTableDataSourceProvider
97 99
implements DataSourceProvider
98 100
{
101
   
102
   /**
103
    * Check if the connection is a proxy delivered by this provider.
104
    * 
105
    * @param   conn
106
    *          The connection to be checked.
107
    *          
108
    * @return  {@code true} if the provided connection is proxied by this provider.
109
    */
110
   public static boolean isProxy(Connection conn)
111
   {
112
      return conn instanceof DataSourceImpl.TempProxyConnection;
113
   }
114
   
115
   /**
116
    * Retrieve the original connection without proxy. Use this with care as it bypassed
117
    * some critical business logic. Avoid executing SQL directly on the physical connection,
118
    * unless you are sure that the wrapper is not affected.
119
    * 
120
    * @param   conn
121
    *          The connection to unwrap.
122
    *          
123
    * @return  The physical connection. The unwrapping works only if this is a proxy of this
124
    *          provider.
125
    */
126
   public static Connection unwrapProxy(Connection conn)
127
   {
128
      if (conn == null || !isProxy(conn))
129
      {
130
         return null;
131
      }
132
      DataSourceImpl.TempProxyConnection proxy = (DataSourceImpl.TempProxyConnection) conn;
133
      return proxy.ctx.physicalConn;
134
   }
135
   
99 136
   /**
100 137
    * Obtain the {@code DataSource} for a specific {@code Database}, within the current context.
101 138
    *
......
110 147
      return new DataSourceImpl();
111 148
   }
112 149
   
150
   
113 151
   /**
114 152
    * Implementation of the {@code DataSource} interface, which maintains context-local data for each
115 153
    * private temp-table connection.
......
177 215
            String url = settings.getString(Settings.URL, null);
178 216
            if (username == null)
179 217
            {
180
               username =settings.getString(Settings.USER, null);
218
               username = settings.getString(Settings.USER, null);
181 219
            }
182 220
            if (password == null)
183 221
            {
......
196 234
         
197 235
         if (ctx.proxyConn == null)
198 236
         {
199
            ctx.proxyConn = new Connection()
200
            {
201
               public void close() throws SQLException
202
               {
203
                  DataSourceImpl.this.close();
204
               }
205
               
206
               public PreparedStatement prepareStatement(String sql)
207
               throws SQLException
208
               {
209
                  // prepare using default result set type and concurrency
210
                  return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
211
               }
212
               
213
               public PreparedStatement prepareStatement(String sql,
214
                                                         int resultSetType,
215
                                                         int resultSetConcurrency)
216
               throws SQLException
217
               {
218
                  PSKey key = new PSKey(sql, resultSetType, resultSetConcurrency);
219
                  UnclosablePreparedStatement ps = ctx.psCache.get(key);
220
                  
221
                  if (ps == null || ps.isClosed())
222
                  {
223
                     if (ps != null)
224
                     {
225
                        if (LOG.isLoggable(Level.SEVERE))
226
                        {
227
                           LOG.log(Level.SEVERE, "Closed statements shouldn't be cached.");
228
                        }
229
                     }
230
                     
231
                     PreparedStatement raw = ctx.physicalConn.prepareStatement(sql, resultSetType, resultSetConcurrency);
232
                     ps = new UnclosablePreparedStatement(raw, false);
233
                     ctx.psCache.put(key, ps);
234
                  }
235
                  else if (ps.isCheckedOut())
236
                  {
237
                     // the prepared statement is already in use
... This diff was truncated because it exceeds the maximum size that can be displayed.