Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.compat.cdc / org.gvsig.fmap.dal / org.gvsig.fmap.dal.db / org.gvsig.fmap.dal.db.jdbc / src / main / java / org / gvsig / fmap / dal / store / jdbc2 / spi / JDBCHelperBase.java @ 46543

History | View | Annotate | Download (48.4 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2020 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 3
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 * For any additional information, do not hesitate to contact us
22
 * at info AT gvsig.com, or visit our website www.gvsig.com.
23
 */
24
package org.gvsig.fmap.dal.store.jdbc2.spi;
25

    
26
import java.io.File;
27
import java.sql.Blob;
28
import java.sql.Clob;
29
import java.sql.ResultSet;
30
import java.util.ArrayList;
31
import java.util.HashSet;
32
import java.util.Iterator;
33
import java.util.List;
34
import java.util.ListIterator;
35
import java.util.function.Predicate;
36
import org.apache.commons.collections.CollectionUtils;
37
import org.apache.commons.collections.iterators.ReverseListIterator;
38
import org.apache.commons.io.IOUtils;
39
import org.apache.commons.lang3.ArrayUtils;
40
import org.apache.commons.lang3.StringUtils;
41
import org.apache.commons.lang3.mutable.MutableBoolean;
42
import org.apache.commons.lang3.mutable.MutableObject;
43
import org.apache.commons.lang3.tuple.ImmutablePair;
44
import org.apache.commons.lang3.tuple.Pair;
45
import org.gvsig.expressionevaluator.Code;
46
import org.gvsig.expressionevaluator.Expression;
47
import org.gvsig.expressionevaluator.ExpressionBuilder;
48
import static org.gvsig.expressionevaluator.ExpressionBuilder.FUNCTION_$CONSTANT;
49
import static org.gvsig.expressionevaluator.ExpressionBuilder.FUNCTION_$IDENTIFIER;
50
import org.gvsig.expressionevaluator.ExpressionEvaluatorLocator;
51
import org.gvsig.expressionevaluator.ExpressionEvaluatorManager;
52
import org.gvsig.expressionevaluator.Function;
53
import org.gvsig.expressionevaluator.GeometryExpressionBuilderHelper.GeometrySupportType;
54
import org.gvsig.expressionevaluator.SymbolTable;
55
import org.gvsig.fmap.dal.DALLocator;
56
import org.gvsig.fmap.dal.DataManager;
57
import static org.gvsig.fmap.dal.DataManager.FUNCTION_EXISTS;
58
import static org.gvsig.fmap.dal.DataManager.FUNCTION_FOREING_VALUE;
59
import org.gvsig.fmap.dal.DataTypes;
60
import org.gvsig.fmap.dal.SQLBuilder;
61
import org.gvsig.fmap.dal.SQLBuilder.Column;
62
import static org.gvsig.fmap.dal.SQLBuilder.PROP_ADD_TABLE_NAME_TO_COLUMNS;
63
import org.gvsig.fmap.dal.SQLBuilder.SelectBuilder;
64
import org.gvsig.fmap.dal.exception.DataException;
65
import org.gvsig.fmap.dal.exception.InitializeException;
66
import org.gvsig.fmap.dal.expressionevaluator.FeatureAttributeEmulatorExpression;
67
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
68
import org.gvsig.fmap.dal.feature.FeatureAttributeEmulator;
69
import org.gvsig.fmap.dal.feature.FeatureQueryOrder;
70
import org.gvsig.fmap.dal.feature.FeatureType;
71
import org.gvsig.fmap.dal.feature.ForeingKey;
72
import org.gvsig.fmap.dal.feature.spi.FeatureProvider;
73
import org.gvsig.fmap.dal.feature.spi.SQLBuilderBase;
74
import org.gvsig.fmap.dal.resource.exception.AccessResourceException;
75
import org.gvsig.fmap.dal.resource.spi.ResourceConsumer;
76
import org.gvsig.fmap.dal.resource.spi.ResourceProvider;
77
import org.gvsig.fmap.dal.serverexplorer.filesystem.FilesystemStoreParameters;
78
import org.gvsig.fmap.dal.spi.DataServerExplorerProviderServices;
79
import org.gvsig.fmap.dal.spi.DataStoreProviderServices;
80
import org.gvsig.fmap.dal.spi.DataTransactionServices;
81
import org.gvsig.fmap.dal.store.db.DBHelper;
82
import org.gvsig.fmap.dal.store.jdbc.JDBCConnectionParameters;
83
import org.gvsig.fmap.dal.store.jdbc.JDBCNewStoreParameters;
84
import org.gvsig.fmap.dal.store.jdbc.JDBCNewStoreParametersBase;
85
import org.gvsig.fmap.dal.store.jdbc.JDBCResourceBase;
86
import org.gvsig.fmap.dal.store.jdbc.JDBCServerExplorerParameters;
87
import org.gvsig.fmap.dal.store.jdbc.JDBCServerExplorerParametersBase;
88
import org.gvsig.fmap.dal.store.jdbc.JDBCStoreParameters;
89
import org.gvsig.fmap.dal.store.jdbc.JDBCStoreParametersBase;
90
import org.gvsig.fmap.dal.store.jdbc.exception.JDBCCantFetchValueException;
91
import org.gvsig.fmap.dal.store.jdbc2.JDBCConnection;
92
import org.gvsig.fmap.dal.store.jdbc2.JDBCHelper;
93
import org.gvsig.fmap.dal.store.jdbc2.JDBCLibrary;
94
import org.gvsig.fmap.dal.store.jdbc2.JDBCServerExplorer;
95
import org.gvsig.fmap.dal.store.jdbc2.JDBCStoreProvider;
96
import org.gvsig.fmap.dal.store.jdbc2.JDBCUtils;
97
import org.gvsig.fmap.dal.store.jdbc2.OperationsFactory;
98
import org.gvsig.fmap.dal.store.jdbc2.ResulSetControler;
99
import org.gvsig.fmap.dal.store.jdbc2.ResulSetControler.ResultSetEntry;
100
import org.gvsig.fmap.dal.store.jdbc2.impl.ResulSetControlerBase;
101
import org.gvsig.fmap.dal.store.jdbc2.spi.expressionbuilder.formatters.ComputedAttribute;
102
import org.gvsig.fmap.dal.store.jdbc2.spi.operations.OperationsFactoryBase;
103
import org.gvsig.fmap.geom.Geometry;
104
import org.gvsig.fmap.geom.GeometryLocator;
105
import org.gvsig.fmap.geom.GeometryManager;
106
import org.gvsig.tools.dispose.impl.AbstractDisposable;
107
import org.gvsig.tools.evaluator.Evaluator;
108
import org.gvsig.tools.exception.BaseException;
109
import org.gvsig.tools.exception.NotYetImplemented;
110
import org.gvsig.tools.locator.LocatorException;
111
import org.gvsig.tools.util.ContainerUtils;
112
import org.gvsig.tools.visitor.FilteredVisitable;
113
import org.gvsig.tools.visitor.VisitCanceledException;
114
import org.gvsig.tools.visitor.Visitor;
115
import org.slf4j.Logger;
116
import org.slf4j.LoggerFactory;
117

    
118
@SuppressWarnings("UseSpecificCatch")
119
public class JDBCHelperBase extends AbstractDisposable implements ResourceConsumer, JDBCHelper {
120

    
121
  private static final boolean ALLOW_AUTOMATIC_VALUES = true;
122
  private static final String QUOTE_FOR_USE_IN_IDENTIFIERS = "\"";
123
  private static final String QUOTE_FOR_USE_IN_STRINGS = "'";
124

    
125
  private static final Logger LOGGER = LoggerFactory.getLogger(JDBCHelperBase.class);
126

    
127
  private ResulSetControler resulSetControler = null;
128

    
129
  // Quien ha creado este helper.
130
  // Se le reenviaran las notificaciones del recurso asociado al helper.
131
  private ResourceConsumer helperClient = null;
132

    
133
  private GeometryManager geometryManager = null;
134

    
135
  private JDBCConnectionParameters connectionParameters;
136

    
137
  private JDBCStoreProvider store;
138

    
139
  protected OperationsFactory operationsFactory = null;
140

    
141
  protected SRSSolver srssolver;
142
  
143
  protected FeatureType providerFeatureType = null;
144
  
145
  protected DataTransactionServices transaction;
146

    
147
  public JDBCHelperBase(JDBCConnectionParameters connectionParameters) {
148
        if (connectionParameters == null) {
149
            throw new IllegalArgumentException("Connection parameters can't be null.");
150
        }
151
        this.connectionParameters = connectionParameters;
152

    
153
        // If a particular treatment is required to convert SRS to the 
154
        // BBDD format, overwrite JDBCSRSsBase and use it instead of JDBCSRSsDumb.
155
        this.srssolver = new SRSSolverDumb(this);
156
    }
157

    
158
  protected void initialize(
159
          ResourceConsumer helperClient,
160
          JDBCConnectionParameters connectionParameters,
161
          JDBCStoreProvider store
162
  ) {
163
    this.store = store;
164
    this.helperClient = helperClient;
165
    this.connectionParameters = connectionParameters;
166
    initializeResource(connectionParameters);
167
  }
168

    
169
  protected String getResourceType() {
170
    return JDBCResourceBase.NAME;
171
  }
172

    
173
  @Override
174
  public String getProviderName() {
175
    return JDBCLibrary.NAME;
176
  }
177

    
178
  @Override
179
  public GeometrySupportType getGeometrySupportType() {
180
    // El proveedor generico de JDBC guadara las geoemtrias como WKT
181
    return GeometrySupportType.WKT;
182
  }
183
  
184
    /**
185
     * @return the providerFeatureType
186
     */
187
  @Override
188
    public FeatureType getProviderFeatureType() {
189
        return providerFeatureType;
190
    }
191

    
192
    /**
193
     * @param providerFeatureType the providerFeatureType to set
194
     */
195
  @Override
196
    public void setProviderFeatureType(FeatureType providerFeatureType) {
197
        this.providerFeatureType = providerFeatureType;
198
    }
199

    
200

    
201
  @Override
202
  public boolean hasSpatialFunctions() {
203
    // Por defecto el proveedor generico de JDBC asume que la BBDD no 
204
    // tiene soporte para funciones espaciales.
205
    return false;
206
  }
207

    
208
  @Override
209
  public boolean allowNestedOperations() {
210
    return false;
211
  }
212

    
213
  @Override
214
  public boolean canWriteGeometry(int geometryType, int geometrySubtype) {
215
    // Como va a guardar las geometrias en WKT, puede escribirlas todas.
216
    return true;
217
  }
218

    
219
  @Override
220
  public JDBCSQLBuilderBase createSQLBuilder() {
221
    return new JDBCSQLBuilderBase(this);
222
  }
223

    
224
  @Override
225
  public String getQuoteForIdentifiers() {
226
    return QUOTE_FOR_USE_IN_IDENTIFIERS;
227
  }
228

    
229
  @Override
230
  public boolean allowAutomaticValues() {
231
    return ALLOW_AUTOMATIC_VALUES;
232
  }
233

    
234
  @Override
235
  public boolean supportOffsetInSelect() {
236
    // Asumimos que la BBDD soporta OFFSET
237
    return true;
238
  }
239

    
240
  @Override
241
  public String getQuoteForStrings() {
242
    return QUOTE_FOR_USE_IN_STRINGS;
243
  }
244

    
245
  protected boolean supportCaller(Code.Callable caller) {
246
    if (StringUtils.equalsIgnoreCase(caller.name(), "FOREING_VALUE")) {
247
      return true;
248
    }
249
    Function function = caller.function();
250
    if (function == null) {
251
      return false;
252
    }
253
    if (!function.isSQLCompatible()) {
254
      return false;
255
    }
256
    if (!this.hasSpatialFunctions()) {
257
      if (StringUtils.equalsIgnoreCase(function.group(), Function.GROUP_OGC)) {
258
        return false;
259
      }
260
    }
261
    return true;
262
  }
263

    
264
  @Override
265
  @SuppressWarnings("Convert2Lambda")
266
  public boolean supportFilter(final FeatureType type, Evaluator filter) {
267
    if (filter == null) {
268
      // No hay que filtrar nada, asi que se p?ede.
269
      return true;
270
    }
271
    String sql = filter.getSQL();
272
    if (StringUtils.isEmpty(sql)) {
273
      // Hay un filtro, pero la SQL es null, con lo que no podemos
274
      // filtrar por el.
275
      return false;
276
    }
277
    return this.supportExpression(type, sql);
278
  }
279
  
280
    @Override
281
    @SuppressWarnings("Convert2Lambda")
282
    public boolean supportExpression(final FeatureType type, String sql) {
283
        // La expression no es valida si:
284
        // - Hay una subquery especificada en los parametros 
285
        // - Si la sql es null.
286
        // - Si se esta usando alguna funcion no-sql en el getSQL
287
        // - Si se esta invocando a metodos de un objeto
288
        // - Si se estan usando funciones OGC y no soportamos funciones espaciales
289
        // - Si se esta usando algun campo calculado en la expresion de filtro.
290
        // 
291
        // Un proveedor especifico podria sobreescribir el metodo,
292
        // para hilar mas fino al comprobar si soporta el filtro o no.
293
        //
294

    
295
        if (StringUtils.isEmpty(sql)) {
296
            return false;
297
        }
298

    
299
//        if (this.useSubquery()) {
300
//            // Se esta usando una subquery en los parametros de acceso a la
301
//            // BBDD, asi que no podemos filtrar.
302
//            return false;
303
//        }
304

    
305
        // Ahora vamos a comprobar que las funciones que se usan son 
306
        // compatibles sql, y que no se usan funciones OGC si el 
307
        // proveedor dice que no soporta funciones espaciales.
308
        // Tambien comprobaremos que el filtro no usa ningun campo calculado.
309
        final MutableBoolean isCompatible = new MutableBoolean(true);
310
        ExpressionEvaluatorManager manager = ExpressionEvaluatorLocator.getManager();
311
        Code code = manager.compile(sql);
312
        SymbolTable symbolTable = manager.createSymbolTable();
313
        code.link(symbolTable);
314
        try {
315
            code.accept(new Visitor() {
316
                @Override
317
                public void visit(Object code_obj) throws VisitCanceledException, BaseException {
318
                    Code code1 = (Code) code_obj;
319
                    switch (code1.code()) {
320
                        case Code.CALLABLE:
321
                            Code.Callable caller = (Code.Callable) code1;
322
                            if (!supportCaller(caller)) {
323
                                isCompatible.setValue(false);
324
                                throw new VisitCanceledException();
325
                            }
326
                            break;
327
                        case Code.METHOD:
328
                            isCompatible.setValue(false);
329
                            throw new VisitCanceledException();
330
                        case Code.IDENTIFIER:
331
                            Code.Identifier identifier = (Code.Identifier) code1;
332
                            if (type != null) {
333
                                FeatureAttributeDescriptor attrdesc = type.getAttributeDescriptor(identifier.name());
334
                                if (attrdesc != null) {
335
                                    if (attrdesc.isComputed()) {
336
                                        FeatureAttributeEmulator emulator = attrdesc.getFeatureAttributeEmulator();
337
                                        if (!(emulator instanceof FeatureAttributeEmulatorExpression)) {
338
                                            isCompatible.setValue(false);
339
                                            throw new VisitCanceledException();
340
                                        }
341
                                        Expression expr = ((FeatureAttributeEmulatorExpression) emulator).getExpression();
342
                                        if (!supportExpression(type, expr.getPhrase())) {
343
                                            isCompatible.setValue(false);
344
                                            throw new VisitCanceledException();
345
                                        }
346
                                    }
347
                                }
348
                            }
349
                            break;
350
                    }
351
                } 
352
            }, new Predicate<FilteredVisitable>() {
353
                @Override
354
                public boolean test(FilteredVisitable code0) {
355
                    if (code0 instanceof Code.Callable) {
356
                        String name = ((Code.Callable) code0).name();
357
                        return StringUtils.equalsIgnoreCase(name, FUNCTION_$CONSTANT)
358
                                || StringUtils.equalsIgnoreCase(name, FUNCTION_$IDENTIFIER);
359
                    }
360
                    return false;
361
                }
362
            });
363

    
364
        } catch (VisitCanceledException ex) {
365
            // Do nothing
366
        } catch (Exception ex) {
367
            LOGGER.warn("Can't calculate if is SQL compatible.", ex);
368
        }
369

    
370
        return isCompatible.booleanValue();
371
    }
372

    
373
  @Override
374
  public boolean supportOrder(FeatureType type, FeatureQueryOrder order) {
375
    if (this.useSubquery()) {
376
      return false;
377
    }
378
    if (order == null) {
379
      return true;
380
    }
381
    for (FeatureQueryOrder.FeatureQueryOrderMember member : order.members()) {
382
      if (member.hasEvaluator()) {
383
        if (!this.supportFilter(type, member.getEvaluator())) {
384
          return false;
385
        }
386
      }
387
    }
388
    return true;
389
  }
390

    
391
  @Override
392
  public OperationsFactory getOperations() {
393
    if (this.operationsFactory == null && !isClosed()) {
394
      this.operationsFactory = new OperationsFactoryBase(this);
395
    }
396
    return operationsFactory;
397
  }
398

    
399
  protected void initializeResource(JDBCConnectionParameters params) {
400
//        Object[] resourceParams = new Object[]{
401
//            params.getUrl(),
402
//            params.getHost(),
403
//            params.getPort(),
404
//            params.getDBName(),
405
//            params.getUser(),
406
//            params.getPassword(),
407
//            params.getJDBCDriverClassName()
408
//        };
409
//
410
//        try {
411
//            ResourceManagerProviderServices manager
412
//                    = (ResourceManagerProviderServices) DALLocator.getResourceManager();
413
//            JDBCResourceBase resource = (JDBCResourceBase) manager.createAddResource(
414
//                    this.getResourceType(),
415
//                    resourceParams
416
//            );
417
//            this.resource = resource;
418
//            this.resource.addConsumer(this);
419
//        } catch (InitializeException ex) {
420
//            logger.trace("Can't initialize resource (" + ArrayUtils.toString(resourceParams) + ").", ex);
421
//            throw new RuntimeException("Can't initialize resource (" + ArrayUtils.toString(resourceParams) + ").", ex);
422
//        }
423

    
424
  }
425

    
426
  @Override
427
  public String getSourceId() {
428
    return this.store.getSourceId();
429
  }
430

    
431
  @Override
432
  public JDBCResourceBase getResource() {
433
    return null;
434
//        return this.resource;
435
  }
436

    
437
  @Override
438
  public JDBCConnection getConnection() throws AccessResourceException {
439
    throw new NotYetImplemented();
440
  }
441

    
442
  @Override
443
  public JDBCConnection getConnectionWritable() throws AccessResourceException {
444
    return this.getConnection();
445
  }
446

    
447
  @Override
448
  public String getConnectionURL() {
449
    return null;
450
  }
451

    
452
  @Override
453
  public JDBCConnectionParameters getConnectionParameters() {
454
    return connectionParameters;
455
  }
456

    
457
  @Override
458
  protected void doDispose() throws BaseException {
459
    JDBCUtils.closeQuietly(this);
460
  }
461

    
462
  @Override
463
  public void close() throws Exception {
464
    JDBCUtils.closeQuietly(this.resulSetControler);
465
    this.resulSetControler = null;
466
    this.operationsFactory = null;
467
    this.srssolver = null;
468
    this.connectionParameters = null;
469
    this.helperClient = null;
470
    this.geometryManager = null;
471
    this.providerFeatureType = null;
472
    this.store = null;
473
    this.transaction = null;
474
  }
475
  
476
  public boolean isClosed() {
477
      return this.srssolver == null;
478
  }
479

    
480
  @Override
481
  public boolean closeResourceRequested(ResourceProvider resource) {
482
    return this.helperClient.closeResourceRequested(resource);
483
  }
484

    
485
  @Override
486
  public void resourceChanged(ResourceProvider resource) {
487
    this.helperClient.resourceChanged(resource);
488
  }
489

    
490
  @Override
491
  public GeometryManager getGeometryManager() {
492
    if (this.geometryManager == null) {
493
      this.geometryManager = GeometryLocator.getGeometryManager();
494
    }
495
    return this.geometryManager;
496
  }
497

    
498
  @Override
499
  public ResulSetControler getResulSetControler() {
500
    if (this.resulSetControler == null) {
501
      this.resulSetControler = new ResulSetControlerBase(this);
502
    }
503
    return this.resulSetControler;
504
  }
505

    
506
  @Override
507
  public void fetchFeature(FeatureProvider feature, ResultSetEntry rs) throws DataException {
508
    fetchFeature(feature, rs.get(), rs.getColumns(), rs.getExtraValueNames());
509
  }
510

    
511
  @Override
512
  public void fetchFeature(FeatureProvider feature, ResultSet rs, FeatureAttributeDescriptor[] columns, String[] extraValueNames) throws DataException {
513
    Object value;
514
    try {
515
      int rsIndex = 1;
516
      for (FeatureAttributeDescriptor column : columns) {
517
        switch (column.getType()) {
518
          case DataTypes.GEOMETRY:
519
            value = this.getGeometryFromColumn(rs, rsIndex++);
520
            break;
521
          default:
522
            value = rs.getObject(rsIndex++);
523
            if (value instanceof Blob) {
524
              Blob blob = (Blob) value;
525
              value = blob.getBytes(1, (int) blob.length());
526
              blob.free();
527
            } else if(value instanceof Clob) {
528
              Clob clob = (Clob) value;
529
              value = new String(IOUtils.toCharArray(clob.getCharacterStream()));
530
              clob.free();
531
          }
532
        }
533
        feature.set(column.getIndex(), value);
534
      }
535
      if (ArrayUtils.isNotEmpty(extraValueNames)) {
536
        feature.setExtraValueNames(extraValueNames);
537
        for (int index = 0; index < extraValueNames.length; index++) {
538
          value = rs.getObject(rsIndex++);
539
          if (value instanceof Blob) {
540
            Blob blob = (Blob) value;
541
            value = blob.getBytes(0, (int) blob.length());
542
            blob.free();
543
          }
544
          feature.setExtraValue(index, value);
545
        }
546
      }
547
    } catch (Exception ex) {
548
      throw new JDBCCantFetchValueException(ex);
549
    }
550
  }
551

    
552
  @Override
553
  public Geometry getGeometryFromColumn(ResultSetEntry rs, int index) throws DataException {
554
    return getGeometryFromColumn(rs.get(), index);
555
  }
556

    
557
  @Override
558
  public Geometry getGeometryFromColumn(ResultSet rs, int index) throws DataException {
559
    try {
560
      Object value;
561
      Geometry geom;
562
      switch (this.getGeometrySupportType()) {
563
        case NATIVE:
564
        case WKB:
565
          value = rs.getBytes(index);
566
          if (value == null) {
567
            return null;
568
          }
569
          geom = this.getGeometryManager().createFrom((byte[]) value);
570
          if( geom == null ) {
571
              LOGGER.debug("Can't get geometry from bytes of column "+index);
572
          }
573
          return geom;
574

    
575

    
576
        case EWKB:
577
          value = rs.getBytes(index);
578
          if (value == null) {
579
            return null;
580
          }
581
          geom = this.getGeometryManager().createFrom((byte[]) value);
582
          if( geom == null ) {
583
              LOGGER.debug("Can't get geometry from bytes of column "+index);
584
          }
585
          return geom;
586
        case WKT:
587
        default:
588
          value = rs.getString(index);
589
          if (value == null) {
590
            return null;
591
          }
592
          geom = this.getGeometryManager().createFrom((String) value);
593
          if( geom == null ) {
594
              LOGGER.debug("Can't get geometry from bytes of column "+index);
595
          }
596
          return geom;
597

    
598
      }
599
    } catch (Exception ex) {
600
      throw new JDBCCantFetchValueException(ex);
601
    }
602
  }
603

    
604
  @Override
605
  public FeatureProvider createFeature(FeatureType featureType) throws DataException {
606
    return this.store.getStoreServices().createDefaultFeatureProvider(featureType);
607
  }
608

    
609
  @Override
610
  public boolean useSubquery() {
611
    if (this.store == null) {
612
      return false;
613
    }
614
    return !StringUtils.isEmpty(this.store.getParameters().getSQL());
615
  }
616

    
617
  @Override
618
  public SRSSolver getSRSSolver() {
619
    return this.srssolver;
620
  }
621

    
622
  @Override
623
  public JDBCStoreProvider createProvider(
624
          JDBCStoreParameters parameters,
625
          DataStoreProviderServices providerServices
626
  ) throws InitializeException {
627

    
628
    JDBCStoreProviderBase theStore = new JDBCStoreProviderBase(
629
            parameters,
630
            providerServices,
631
            DBHelper.newMetadataContainer(JDBCLibrary.NAME),
632
            this
633
    );
634
    this.initialize(theStore, parameters, theStore);
635
    return theStore;
636
  }
637

    
638
  @Override
639
  public JDBCServerExplorer createServerExplorer(
640
          JDBCServerExplorerParameters parameters,
641
          DataServerExplorerProviderServices providerServices
642
  ) throws InitializeException {
643

    
644
    JDBCServerExplorer explorer = new JDBCServerExplorerBase(
645
            parameters,
646
            providerServices,
647
            this
648
    );
649
    this.initialize(explorer, parameters, null);
650
    return explorer;
651
  }
652

    
653
  @Override
654
  public JDBCNewStoreParameters createNewStoreParameters() {
655
    return new JDBCNewStoreParametersBase();
656
  }
657

    
658
  @Override
659
  public JDBCStoreParameters createOpenStoreParameters() {
660
    return new JDBCStoreParametersBase();
661
  }
662
  
663
  @Override
664
  public JDBCStoreParameters createOpenStoreParameters(JDBCServerExplorerParameters parameters) {
665
    JDBCStoreParameters params = this.createOpenStoreParameters();
666
    params.setHost(parameters.getHost());
667
    params.setPort(parameters.getPort());
668
    params.setDBName(parameters.getDBName());
669
    params.setUser(parameters.getUser());
670
    params.setPassword(parameters.getPassword());
671
    params.setCatalog(parameters.getCatalog());
672
    params.setSchema(parameters.getSchema());
673
    params.setJDBCDriverClassName(parameters.getJDBCDriverClassName());
674
    params.setUrl(parameters.getUrl());
675
    if( parameters instanceof FilesystemStoreParameters ) {
676
        File f = ((FilesystemStoreParameters) parameters).getFile();
677
        ((FilesystemStoreParameters) params).setFile(f);
678
    }
679
    params.setBatchSize(parameters.getBatchSize());
680
    return params;
681
  }
682
  
683

    
684
  @Override
685
  public JDBCServerExplorerParameters createServerExplorerParameters() {
686
    return new JDBCServerExplorerParametersBase();
687
  }
688

    
689
  @Override
690
  public String getSourceId(JDBCStoreParameters parameters) {
691
    return parameters.getHost() + ":"
692
            + parameters.getDBName() + ":"
693
            + parameters.getSchema() + ":"
694
            + parameters.tableID();
695
  }
696

    
697
  @Override
698
  public boolean isThreadSafe() {
699
    return true;
700
  }
701

    
702
  /** 
703
   * This method has been overriden in Oracle provider
704
   * 
705
   * @param sqlbuilder
706
   * @param type
707
   * @param extra_column_names 
708
   */
709
  @Override
710
  public void processSpecialFunctions(
711
          SQLBuilder sqlbuilder,
712
          FeatureType type,
713
          List<String> extra_column_names) {
714
    replaceForeingValueFunction(sqlbuilder, type, extra_column_names);
715
    replaceExistsFunction(sqlbuilder, type, extra_column_names);
716
    addTableToColumnReferences(sqlbuilder, type);
717
  }
718

    
719
  @SuppressWarnings("Convert2Lambda")
720
  protected void replaceExistsFunction(
721
          SQLBuilder sqlbuilder,
722
          FeatureType type,
723
          final List<String> extra_column_names) {
724
    
725
    //  Si lse encuentra una construccion del tipo:
726
    //    SELECT ... FROM ... WHERE ... EXISTS(list, 'EXISTS_ID') ...
727
    //  se traslada a:
728
    //    SELECT ... EXISTS(list) AS EXISTS_ID FROM ... WHERE ... EXISTS(list) ...
729
    //  Y se a?ade el valor ESISTS_ID a las columnas extra a recuperar de la consulta.
730
    //          
731
    
732
    final SQLBuilder.SelectBuilder select = sqlbuilder.select();
733
    final ExpressionBuilder where = select.where();
734
    if (where == null || where.isEmpty() || select.has_group_by() ) {
735
      return;
736
    }
737
    final List<ExpressionBuilder.Value[]> value_replacements = new ArrayList<>();
738
    where.accept(new ExpressionBuilder.Visitor() {
739
      @Override
740
      public void visit(ExpressionBuilder.Visitable value) {
741
        if (!(value instanceof ExpressionBuilder.Function)) {
742
          return;
743
        }
744
        ExpressionBuilder.Function function = (ExpressionBuilder.Function) value;
745
        if (!StringUtils.equalsIgnoreCase(function.name(), FUNCTION_EXISTS)) {
746
          return;
747
        }
748
        if (function.parameters().size() != 2) {
749
          return;
750
        }
751
        ExpressionBuilder.Value arg0 = function.parameters().get(0);
752
        ExpressionBuilder.Value arg1 = function.parameters().get(1);
753
        if (arg1 == null) {
754
          return;
755
        }
756
        String columnName = (String) ((ExpressionBuilder.Constant) arg1).value();
757
        SQLBuilder.SelectColumnBuilder column = select.column();
758
        column.value(
759
                sqlbuilder.expression().function(FUNCTION_EXISTS, arg0)
760
        );
761
        column.as(columnName);
762

    
763
        if( extra_column_names!=null ) {
764
          extra_column_names.add(columnName);
765
        }
766
      }
767
    }, null);
768
    if (value_replacements.isEmpty()) {
769
      return;
770
    }
771
    // Realizamos los reemplazos calculados previamente (value_replacements).
772
    for (ExpressionBuilder.Value[] replaceValue : value_replacements) {
773
      ExpressionBuilder.Value target = replaceValue[0];
774
      ExpressionBuilder.Value replacement = replaceValue[1];
775
      sqlbuilder.select().replace(target, replacement);
776
    }
777
  }
778

    
779
  @SuppressWarnings("Convert2Lambda")
780
  protected void replaceForeingValueFunction(
781
          SQLBuilder sqlbuilder,
782
          FeatureType type,
783
          List<String> extra_column_names) {
784
    try {
785
      // See test SQLBuilderTest->testForeingValue()
786
      final ExpressionBuilder where = sqlbuilder.select().where();
787
//      if (where == null || where.isEmpty()) {
788
//        return;
789
//      }
790
      final SQLBuilder.TableNameBuilder table = sqlbuilder.select().from().table();
791
      final ExpressionBuilder expbuilder = sqlbuilder.expression();
792

    
793
      final List<String> foreing_value_args;
794
      if (extra_column_names == null || sqlbuilder.select().has_group_by() || sqlbuilder.select().has_aggregate_functions() ) {
795
        foreing_value_args = new ArrayList<>();
796
      } else {
797
        foreing_value_args = extra_column_names;
798
      }
799
      final List<ExpressionBuilder.Value[]> value_replacements = new ArrayList<>();
800

    
801
      MutableObject<Boolean> hasForeignValueFunctions = new MutableObject<>(false);
802
      
803
      // Buscamos las llamadas a la funcion "foreing_value" y nos quedamos
804
      // el argumento de esta asi como por que tendriamos que sustituirla 
805
      // una vez hechos los left joins que toquen.
806
      sqlbuilder.select().accept(new ExpressionBuilder.Visitor() {
807
        @Override
808
        public void visit(ExpressionBuilder.Visitable value) {
809
          // Requiere que sea la funcion "FOREING_VALUE con un solo 
810
          // argumento que sea una constante de tipo string.
811
          if (!(value instanceof ExpressionBuilder.Function)) {
812
            return;
813
          }
814
          ExpressionBuilder.Function function = (ExpressionBuilder.Function) value;
815
          if (!StringUtils.equalsIgnoreCase(function.name(), FUNCTION_FOREING_VALUE)) {
816
            return;
817
          }
818
          if (function.parameters().size() != 1) {
819
            return;
820
          }
821
          ExpressionBuilder.Value arg = function.parameters().get(0);
822
          if (!(arg instanceof ExpressionBuilder.Constant)) {
823
            return;
824
          }
825
          Object arg_value = ((ExpressionBuilder.Constant) arg).value();
826
          if (!(arg_value instanceof CharSequence)) {
827
            return;
828
          }
829
          hasForeignValueFunctions.setValue(true);
830
          String foreing_value_arg = arg_value.toString();
831
          String[] foreingNameParts = StringUtils.split(foreing_value_arg, "[.]");
832
          if (foreingNameParts.length != 2) {
833
            // De momento solo tratamos joins entre dos tablas.
834
            return;
835
          }
836
          String columnNameLocal = foreingNameParts[0];
837
          String columnNameForeing = foreingNameParts[1];
838
          FeatureAttributeDescriptor attr = type.getAttributeDescriptor(columnNameLocal);
839
          if (attr==null) {
840
              throw new RuntimeException("Cannot find in feature type attribute:"+columnNameLocal);
841
          }
842
          if (!attr.isForeingKey()) {
843
            // Uhm... si el argumento no referencia a un campo que es
844
            // clave ajena no lo procesamos. 
845
            // ? Deberiamos lanzar un error ?
846
            return;
847
          }
848
          // Nos guardaremos por que hay que reemplazar la funcion 
849
          // FOREING_VALUE, y su argumento para mas tarde.
850
          ForeingKey foreingKey = attr.getForeingKey();
851
          SQLBuilder.TableNameBuilder foreingTable = sqlbuilder.createTableNameBuilder()
852
                  .database(table.getDatabase())
853
                  .schema(table.getSchema())
854
                  .name(foreingKey.getTableName());
855
          // Reemplzaremos la funcion FOREING_VALUE, por el acceso al campo
856
          // que toca de la tabla a la que referencia la clave ajena.
857
          ExpressionBuilder.Variable function_replacement = sqlbuilder.column(foreingTable, columnNameForeing);
858
          value_replacements.add(
859
                  new ExpressionBuilder.Value[]{
860
                    function,
861
                    function_replacement
862
                  }
863
          );
864
          if (!foreing_value_args.contains(foreing_value_arg)) {
865
            foreing_value_args.add(foreing_value_arg);
866
          }
867
        }
868
      }, null);
869
      
870
      // Si no habia ningun llamada a la funcion FOREING_VALUE, no hay que
871
      // hacer nada.
872
      if ( !hasForeignValueFunctions.getValue() || foreing_value_args.isEmpty()) {
873
        return;
874
      }
875

    
876
      // Calculamos que referencias de columnas hemos de cambiar para 
877
      // que no aparezcan ambiguedades al hacer el join (nombres de
878
      // columna de una y otra tabla que coincidan).
879
      // Para las columnas que puedan dar conflicto se prepara un reemplazo 
880
      // de estas que tenga el nombre de tabla.
881
      for (ExpressionBuilder.Variable variable : sqlbuilder.variables()) {
882
        if (variable == null || variable instanceof SQLBuilderBase.ColumnBase) {
883
          continue;
884
        }
885
        if (ContainerUtils.contains(extra_column_names, variable.name(), ContainerUtils.EQUALS_IGNORECASE_COMPARATOR)) {
886
            continue;
887
        }
888
        boolean alreadyReplaced = false;
889
        for (String foreingName : foreing_value_args) {
890
          String[] foreingNameParts = foreingName.split("[.]");
891
          if (foreingNameParts.length != 2) {
892
            continue;
893
          }
894
          String columnNameLocal = foreingNameParts[0];
895
          String columnNameForeing = foreingNameParts[1];
896
          if (StringUtils.equalsIgnoreCase(variable.name(), columnNameForeing)) {
897
              if(alreadyReplaced){
898
                  throw new RuntimeException("Column reference \""+ columnNameForeing+"\" is ambiguous");
899
              }
900
              alreadyReplaced = true;
901
              
902
              FeatureAttributeDescriptor attr = type.getAttributeDescriptor(columnNameLocal);
903
              if (attr == null) {
904
                  throw new RuntimeException("Cannot find in feature type attribute:" + columnNameLocal);
905
              }
906
              if (attr.isForeingKey()) {
907
                  // Nos guardaremos por que hay que reemplazar la funcion 
908
                  // FOREING_VALUE, y su argumento para mas tarde.
909
                  ForeingKey foreingKey = attr.getForeingKey();
910
                  SQLBuilder.TableNameBuilder foreingTable = sqlbuilder.createTableNameBuilder()
911
                          .database(table.getDatabase())
912
                          .schema(table.getSchema())
913
                          .name(foreingKey.getTableName());
914
                  ExpressionBuilder.Variable variable_replacement = sqlbuilder.column(
915
                          foreingTable,
916
                          variable.name()
917
                  );
918
                  value_replacements.add(
919
                          new ExpressionBuilder.Value[]{
920
                              variable,
921
                              variable_replacement
922
                          }
923
                  );
924
              }
925
          }
926
          if (StringUtils.equalsIgnoreCase(variable.name(), columnNameLocal)) {
927
            ExpressionBuilder.Variable variable_replacement = sqlbuilder.column(
928
                    table,
929
                    variable.name()
930
            );
931
            value_replacements.add(
932
                    new ExpressionBuilder.Value[]{
933
                      variable,
934
                      variable_replacement
935
                    }
936
            );
937
          }
938
        }
939
      }
940

    
941
      // Realizamos los reemplazos calculados previamente (value_replacements).
942
      for (ExpressionBuilder.Value[] replaceValue : value_replacements) {
943
        ExpressionBuilder.Value target = replaceValue[0];
944
        ExpressionBuilder.Value replacement = replaceValue[1];
945
        sqlbuilder.select().replace(target, replacement);
946
      }
947

    
948
        // A?adimos a la SQL los "LEFT JOIN" que toca para poder acceder
949
        // a los valores referenciados por la funcion FOREING_VALUE.
950
        // Ademas a?adimos los valores referenciados por la funcion FOREING_VALUE
951
        // como columnas de la SQL para poder acceder a ellos si fuese necesario.
952
        HashSet usedLeftJoins = new HashSet();
953
      for (String foreingName : foreing_value_args) {
954
        String[] foreingNameParts = foreingName.split("[.]");
955
        if (foreingNameParts.length != 2) {
956
          continue;
957
        }
958
        String columnNameLocal = foreingNameParts[0];
959
        String columnNameForeing = foreingNameParts[1];
960
        FeatureAttributeDescriptor attr = type.getAttributeDescriptor(columnNameLocal);
961
        if (attr.isForeingKey()) {
962
          ForeingKey foreingKey = attr.getForeingKey();
963
          SQLBuilder.TableNameBuilder foreingTable = sqlbuilder.createTableNameBuilder()
964
                  .database(table.getDatabase())
965
                  .schema(table.getSchema())
966
                  .name(foreingKey.getTableName());
967
          SQLBuilder.TableNameBuilder mainTable = sqlbuilder.createTableNameBuilder()
968
                  .database(table.getDatabase())
969
                  .schema(table.getSchema())
970
                  .name(table.getName());
971
          
972
          if (!usedLeftJoins.contains(foreingTable.getName())) {
973
            sqlbuilder.select().from()
974
                    .left_join(
975
                            foreingTable,
976
                            expbuilder.eq(
977
                                    sqlbuilder.column(mainTable, columnNameLocal),
978
                                    sqlbuilder.column(foreingTable, foreingKey.getCodeName())
979
                            )
980
                    );
981
            usedLeftJoins.add(foreingTable.getName());
982
          }
983
          //No est? claro si debe a?adirse esta columna o no, OJO quitarlo o ponerlo altera los test de  H2
984
          //Si se comentarizan ojo con extra_column_names ya que se habr?n a?adido columnas que no estar?n en la select
985
          if ( !(sqlbuilder.select().has_group_by() || sqlbuilder.select().has_aggregate_functions()) ) {
986
                sqlbuilder.select().column().name(foreingTable, columnNameForeing);
987
          }
988
        }
989
      }
990
      
991
    } catch (Throwable th) {
992
      LOGGER.warn("Can't replace FOREING_VALUE function.", th);
993
      throw th;
994
    } finally {
995
//      LOGGER.trace("Exit from replaceForeingValueFunction.");
996
    }
997
  }
998
 
999
    protected void addTableToColumnReferences(SQLBuilder sqlbuilder, FeatureType type) {
1000
        SelectBuilder select = sqlbuilder.select();
1001
        List<Pair<SQLBuilder.TableNameBuilder, FeatureType>> tables = new ArrayList<>();
1002
        collectTablesFromSelect(select, tables, type);
1003

    
1004
        addTableToColumnReferencesInSingleSelect(
1005
                sqlbuilder, 
1006
                select, 
1007
                tables
1008
        );
1009
        
1010
    }
1011

    
1012
    private void collectTablesFromSelect(SelectBuilder select, List<Pair<SQLBuilder.TableNameBuilder, FeatureType>> tables, FeatureType type) throws LocatorException {
1013
        DataManager dataManager = DALLocator.getDataManager();
1014
        
1015
        List<SQLBuilder.JoinBuilder> joins = select.from().getJoins();
1016
        if(!CollectionUtils.isEmpty(joins)){
1017
            for (SQLBuilder.JoinBuilder join : joins) {
1018
                SQLBuilder.TableNameBuilder joinTable = join.getTable();
1019
                FeatureType featureType = dataManager.getStoresRepository().getFeatureType(joinTable.getName());
1020
                if(featureType != null){
1021
                    tables.add(new ImmutablePair<>(joinTable,featureType));
1022
                }
1023
            }
1024
        }
1025
        SQLBuilder.TableNameBuilder table = select.from().table();
1026
        if(type == null){
1027
            type = dataManager.getStoresRepository().getFeatureType(table.getName());
1028
            
1029
        }
1030
        if(type != null){
1031
            tables.add(new ImmutablePair<>(table,type));
1032
        }
1033
    }
1034
    
1035
    protected void addTableToColumnReferencesInSingleSelect(SQLBuilder sqlbuilder, SelectBuilder select, List<Pair<SQLBuilder.TableNameBuilder,FeatureType>> outerTables) {
1036
        
1037
        final SQLBuilder.TableNameBuilder fromTable = select.from().table();
1038

    
1039
        final List<ExpressionBuilder.Value[]> value_replacements = new ArrayList<>();
1040
        List<ExpressionBuilder.Variable> variables = new ArrayList<>();
1041
//        List<ExpressionBuilder.Value> variablesToExclude = new ArrayList<>();
1042
        
1043
        select.accept((ExpressionBuilder.Visitable value) -> {
1044
            if(value instanceof Column) {
1045
                Column c = (Column)value;
1046
                if(c.table() == null || !c.table().has_name() || !c.table().has_schema()){
1047
                    variables.add(c);
1048
                }
1049
                
1050
            } else if(value instanceof ExpressionBuilder.Variable) {
1051
                variables.add((ExpressionBuilder.Variable) value);
1052
            }
1053

    
1054
        }, new ExpressionBuilder.VisitorFilter() {
1055
            @Override
1056
            public boolean skipChildren() {
1057
                return true;
1058
            }
1059
            
1060
            @Override
1061
            public boolean accept(ExpressionBuilder.Visitable visitable) {
1062
                if(select == visitable){
1063
                    return true;
1064
                }
1065
                if(visitable instanceof SelectBuilder) {
1066
                    ArrayList<Pair<SQLBuilder.TableNameBuilder, FeatureType>> tables = new ArrayList<>(outerTables);
1067
                    collectTablesFromSelect((SelectBuilder) visitable, tables, null);
1068
                    
1069
                    addTableToColumnReferencesInSingleSelect(sqlbuilder, (SelectBuilder) visitable, tables);
1070
                    return false;
1071
                }
1072
                return true;
1073
            }
1074
        });
1075
        
1076
        for (ExpressionBuilder.Variable variable : variables) {
1077
            ExpressionBuilder.Variable variable_replacement = null;
1078
            
1079
            Pair<SQLBuilder.TableNameBuilder, FeatureType> tableNameAndFeatureType = getTableAndFeatureType(outerTables, variable.name());
1080
            if (tableNameAndFeatureType != null) {
1081
                SQLBuilder.TableNameBuilder variableTable = tableNameAndFeatureType.getLeft();
1082

    
1083
                if (variable instanceof SQLBuilder.Column) {
1084
                    Column column = (SQLBuilder.Column) variable;
1085
                    if (column.table() != null && column.table().has_name()) {
1086
                        if (column.table().has_schema()) {
1087
                            //La columna tiene tabla y esquema => No hacemos nada
1088
                        } else {
1089
                            if(column.table().getName().equals(variableTable.getName())){
1090
                                SQLBuilder.TableNameBuilder t = sqlbuilder.createTableNameBuilder()
1091
                                        .name(column.table().getName())
1092
                                        .schema(variableTable.getSchema());
1093
                                variable_replacement = sqlbuilder.column_from(
1094
                                        t,
1095
                                        variable
1096
                                );
1097
                            } else {
1098
                                if(fromTable.has_schema()){
1099
                                    SQLBuilder.TableNameBuilder t = sqlbuilder.createTableNameBuilder()
1100
                                            .name(column.table().getName())
1101
                                            .schema(fromTable.getSchema()); //Mismo esquema que en el FROM
1102
                                    variable_replacement = sqlbuilder.column_from(
1103
                                            t,
1104
                                            variable
1105
                                    );
1106
                                } else {
1107
                                    // No tiene esquema ni podemos averiguarlo => No hacemos nada
1108
                                }
1109
                            }
1110
                        }
1111
                    } else {
1112
                        column = sqlbuilder.column_from(tableNameAndFeatureType.getLeft(), variable);
1113
                        column.setProperty(SQLBuilder.PROP_FEATURE_TYPE, tableNameAndFeatureType.getRight());
1114
                        variable_replacement = column;
1115
                    }
1116
                } else {
1117
                    Column column = sqlbuilder.column_from(tableNameAndFeatureType.getLeft(), variable);
1118
                    column.setProperty(SQLBuilder.PROP_FEATURE_TYPE, tableNameAndFeatureType.getRight());
1119
                    variable_replacement = column;
1120
                }
1121

    
1122
            } else {
1123
                if (variable instanceof SQLBuilder.Column) {
1124
                    Column column = (SQLBuilder.Column) variable;
1125
                    if (column.table() == null || !column.table().has_name()) {
1126
                        variable_replacement = sqlbuilder.column_from(
1127
                                fromTable,
1128
                                variable
1129
                        );
1130
                    } else if (!column.table().has_schema()) {
1131
                        if(fromTable.has_schema()){
1132
                            SQLBuilder.TableNameBuilder t = sqlbuilder.createTableNameBuilder()
1133
                                    .name(column.table().getName())
1134
                                    .schema(fromTable.getSchema()); //Mismo esquema que en el FROM
1135
                            variable_replacement = sqlbuilder.column_from(
1136
                                    t,
1137
                                    variable
1138
                            );
1139
                        } else {
1140
                            // No tiene esquema ni podemos averiguarlo => No hacemos nada
1141
                        }
1142
                    }
1143
                } else {
1144
                    variable_replacement = sqlbuilder.column_from(
1145
                            fromTable,
1146
                            variable
1147
                    );
1148
                }
1149
            }
1150
            if(variable_replacement != null){
1151
                value_replacements.add(
1152
                        new ExpressionBuilder.Value[]{
1153
                            variable,
1154
                            variable_replacement
1155
                        }
1156
                );
1157
            }
1158

    
1159
        }
1160

    
1161
        for (ExpressionBuilder.Value[] replaceValue : value_replacements) {
1162
            ExpressionBuilder.Value target = replaceValue[0];
1163
            ExpressionBuilder.Value replacement = replaceValue[1];
1164
            Boolean addTableName = (Boolean) target.getProperty(PROP_ADD_TABLE_NAME_TO_COLUMNS);
1165
            if(addTableName == null || !addTableName){
1166
                continue;
1167
            }
1168
            select.replace(target, replacement);
1169
        }
1170

    
1171
    }
1172
    
1173
    protected Pair<SQLBuilder.TableNameBuilder, FeatureType> getTableAndFeatureType(List<Pair<SQLBuilder.TableNameBuilder, FeatureType>> outerTables, String columnName) {
1174
//        ListIterator<Pair<SQLBuilder.TableNameBuilder, FeatureType>> it = outerTables.listIterator(outerTables.size());
1175
//        
1176
//        while (it.hasPrevious()) {
1177
//            Pair<SQLBuilder.TableNameBuilder, FeatureType> pair = it.previous();
1178
//            FeatureType featureType = pair.getRight();
1179
//            if(featureType.get(columnName) != null){
1180
//                return pair;
1181
//            }
1182
//        }
1183
        
1184
//        ListIterator<Pair<SQLBuilder.TableNameBuilder, FeatureType>> it = new ReverseListIterator(outerTables);
1185
//        while (it.hasNext()) {
1186
//            Pair<SQLBuilder.TableNameBuilder, FeatureType> pair = it.next();
1187
//            FeatureType featureType = pair.getRight();
1188
//            if(featureType.get(columnName) != null){
1189
//                return pair;
1190
//            }
1191
//        }
1192
        
1193
        for (Iterator<Pair<SQLBuilder.TableNameBuilder, FeatureType>> iterator = new ReverseListIterator(outerTables); iterator.hasNext();) {
1194
            Pair<SQLBuilder.TableNameBuilder, FeatureType> pair = iterator.next();
1195
            FeatureType featureType = pair.getRight();
1196
            if(featureType.get(columnName) != null){
1197
                return pair;
1198
            }
1199
        }
1200

    
1201
        return null;
1202
    }
1203
    
1204
    @Override
1205
    public void setTransaction(DataTransactionServices transaction) {
1206
        this.transaction = transaction;
1207
    }
1208
    
1209
    @Override
1210
    public String toString() {
1211
        try {
1212
            return String.format("%s %x %s", this.getClass().getSimpleName(), this.hashCode(), this.connectionParameters.getUrl());
1213
        } catch (Exception e) {
1214
            return super.toString();
1215
        }
1216
    }
1217

    
1218
  @Override
1219
    public String getConnectionProviderStatus() {
1220
        return "";
1221
    }
1222
        
1223
  @Override
1224
        public void expandCalculedColumns(JDBCSQLBuilderBase sqlbuilder) {
1225
            ComputedAttribute computedAttributeFormater = new ComputedAttribute(sqlbuilder, sqlbuilder.formatter());
1226
            for (int i = 0; i < 10; i++) {
1227
                List<Pair<ExpressionBuilder.Variable, ExpressionBuilder.Value>> variablesToReplace = new ArrayList<>();
1228
                sqlbuilder.accept((ExpressionBuilder.Visitable value) -> {
1229
                    if (computedAttributeFormater.canApply((ExpressionBuilder.Value) value)) {
1230
                        ExpressionBuilder.Variable variable = (ExpressionBuilder.Variable) value;
1231
                        ExpressionBuilder.Value replace = computedAttributeFormater.expandedValue(variable);
1232
                        variablesToReplace.add(Pair.of(variable, replace));
1233
                    }
1234
                }, null);
1235
                if (variablesToReplace.isEmpty()) {
1236
                    break;
1237
                } 
1238
                for (Pair<ExpressionBuilder.Variable, ExpressionBuilder.Value> entry : variablesToReplace) {
1239
                    ExpressionBuilder.Value variable = entry.getKey();
1240
                    ExpressionBuilder.Value replace = entry.getValue();
1241
                    Boolean addTableName = (Boolean) variable.getProperty(PROP_ADD_TABLE_NAME_TO_COLUMNS);
1242
                    if (addTableName != null && addTableName) {
1243
                        sqlbuilder.setProperties(replace, null, PROP_ADD_TABLE_NAME_TO_COLUMNS,true);
1244
                    }
1245
                    sqlbuilder.select().replace(variable, replace);
1246
                }
1247
            }
1248
    }
1249
        
1250
    @Override
1251
    public DataTransactionServices getTransaction() {
1252
        return this.transaction;
1253
    }
1254

    
1255
    public ConnectionProvider getConnectionProvider() {
1256
        return new FakeConnectionProvider(connectionParameters);
1257
    }
1258
}