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 @ 45647

History | View | Annotate | Download (30.7 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.Connection;
30
import java.sql.ResultSet;
31
import java.util.ArrayList;
32
import java.util.HashSet;
33
import java.util.List;
34
import org.apache.commons.io.IOUtils;
35
import org.apache.commons.lang3.ArrayUtils;
36
import org.apache.commons.lang3.StringUtils;
37
import org.apache.commons.lang3.mutable.MutableBoolean;
38
import org.gvsig.expressionevaluator.Code;
39
import org.gvsig.expressionevaluator.ExpressionBuilder;
40
import org.gvsig.expressionevaluator.ExpressionEvaluatorLocator;
41
import org.gvsig.expressionevaluator.ExpressionEvaluatorManager;
42
import org.gvsig.expressionevaluator.Function;
43
import org.gvsig.expressionevaluator.GeometryExpressionBuilderHelper.GeometrySupportType;
44
import org.gvsig.expressionevaluator.SymbolTable;
45
import static org.gvsig.fmap.dal.DataManager.FUNCTION_EXISTS;
46
import static org.gvsig.fmap.dal.DataManager.FUNCTION_FOREING_VALUE;
47
import org.gvsig.fmap.dal.DataTypes;
48
import org.gvsig.fmap.dal.SQLBuilder;
49
import org.gvsig.fmap.dal.exception.DataException;
50
import org.gvsig.fmap.dal.exception.InitializeException;
51
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
52
import org.gvsig.fmap.dal.feature.FeatureQueryOrder;
53
import org.gvsig.fmap.dal.feature.FeatureType;
54
import org.gvsig.fmap.dal.feature.ForeingKey;
55
import org.gvsig.fmap.dal.feature.spi.FeatureProvider;
56
import org.gvsig.fmap.dal.feature.spi.SQLBuilderBase;
57
import org.gvsig.fmap.dal.resource.exception.AccessResourceException;
58
import org.gvsig.fmap.dal.resource.spi.ResourceConsumer;
59
import org.gvsig.fmap.dal.resource.spi.ResourceProvider;
60
import org.gvsig.fmap.dal.serverexplorer.filesystem.FilesystemStoreParameters;
61
import org.gvsig.fmap.dal.spi.DataServerExplorerProviderServices;
62
import org.gvsig.fmap.dal.spi.DataStoreProviderServices;
63
import org.gvsig.fmap.dal.store.db.DBHelper;
64
import org.gvsig.fmap.dal.store.jdbc.JDBCConnectionParameters;
65
import org.gvsig.fmap.dal.store.jdbc.JDBCLibrary;
66
import org.gvsig.fmap.dal.store.jdbc.JDBCNewStoreParameters;
67
import org.gvsig.fmap.dal.store.jdbc.JDBCResource;
68
import org.gvsig.fmap.dal.store.jdbc.JDBCServerExplorerParameters;
69
import org.gvsig.fmap.dal.store.jdbc.JDBCStoreParameters;
70
import org.gvsig.fmap.dal.store.jdbc.exception.JDBCCantFetchValueException;
71
import org.gvsig.fmap.dal.store.jdbc2.JDBCHelper;
72
import org.gvsig.fmap.dal.store.jdbc2.JDBCServerExplorer;
73
import org.gvsig.fmap.dal.store.jdbc2.JDBCStoreProvider;
74
import org.gvsig.fmap.dal.store.jdbc2.JDBCUtils;
75
import org.gvsig.fmap.dal.store.jdbc2.OperationsFactory;
76
import org.gvsig.fmap.dal.store.jdbc2.ResulSetControler;
77
import org.gvsig.fmap.dal.store.jdbc2.ResulSetControler.ResultSetEntry;
78
import org.gvsig.fmap.dal.store.jdbc2.impl.ResulSetControlerBase;
79
import org.gvsig.fmap.dal.store.jdbc2.spi.operations.OperationsFactoryBase;
80
import org.gvsig.fmap.geom.Geometry;
81
import org.gvsig.fmap.geom.GeometryLocator;
82
import org.gvsig.fmap.geom.GeometryManager;
83
import org.gvsig.tools.dispose.impl.AbstractDisposable;
84
import org.gvsig.tools.evaluator.Evaluator;
85
import org.gvsig.tools.exception.BaseException;
86
import org.gvsig.tools.exception.NotYetImplemented;
87
import org.gvsig.tools.visitor.VisitCanceledException;
88
import org.gvsig.tools.visitor.Visitor;
89
import org.slf4j.Logger;
90
import org.slf4j.LoggerFactory;
91

    
92
@SuppressWarnings("UseSpecificCatch")
93
public class JDBCHelperBase extends AbstractDisposable implements ResourceConsumer, JDBCHelper {
94

    
95
  private static final boolean ALLOW_AUTOMATIC_VALUES = true;
96
  private static final String QUOTE_FOR_USE_IN_IDENTIFIERS = "\"";
97
  private static final String QUOTE_FOR_USE_IN_STRINGS = "'";
98

    
99
  private static final Logger LOGGER = LoggerFactory.getLogger(JDBCHelperBase.class);
100

    
101
  private ResulSetControler resulSetControler = null;
102

    
103
  // Quien ha creado este helper.
104
  // Se le reenviaran las notificaciones del recurso asociado al helper.
105
  private ResourceConsumer helperClient = null;
106

    
107
  private GeometryManager geometryManager = null;
108

    
109
  private JDBCConnectionParameters connectionParameters;
110

    
111
  private JDBCStoreProvider store;
112

    
113
  protected OperationsFactory operationsFactory = null;
114

    
115
  protected SRSSolver srssolver;
116
  
117
  protected FeatureType providerFeatureType = null;
118

    
119
  public JDBCHelperBase(JDBCConnectionParameters connectionParameters) {
120
        if (connectionParameters == null) {
121
            throw new IllegalArgumentException("Connection parameters can't be null.");
122
        }
123
        this.connectionParameters = connectionParameters;
124

    
125
        // If a particular treatment is required to convert SRS to the 
126
        // BBDD format, overwrite JDBCSRSsBase and use it instead of JDBCSRSsDumb.
127
        this.srssolver = new SRSSolverDumb(this);
128
    }
129

    
130
  protected void initialize(
131
          ResourceConsumer helperClient,
132
          JDBCConnectionParameters connectionParameters,
133
          JDBCStoreProvider store
134
  ) {
135
    this.store = store;
136
    this.helperClient = helperClient;
137
    this.connectionParameters = connectionParameters;
138
    initializeResource(connectionParameters);
139
  }
140

    
141
  protected String getResourceType() {
142
    return JDBCResource.NAME;
143
  }
144

    
145
  @Override
146
  public String getProviderName() {
147
    return JDBCLibrary.NAME;
148
  }
149

    
150
  @Override
151
  public GeometrySupportType getGeometrySupportType() {
152
    // El proveedor generico de JDBC guadara las geoemtrias como WKT
153
    return GeometrySupportType.WKT;
154
  }
155
  
156
    /**
157
     * @return the providerFeatureType
158
     */
159
  @Override
160
    public FeatureType getProviderFeatureType() {
161
        return providerFeatureType;
162
    }
163

    
164
    /**
165
     * @param providerFeatureType the providerFeatureType to set
166
     */
167
  @Override
168
    public void setProviderFeatureType(FeatureType providerFeatureType) {
169
        this.providerFeatureType = providerFeatureType;
170
    }
171

    
172

    
173
  @Override
174
  public boolean hasSpatialFunctions() {
175
    // Por defecto el proveedor generico de JDBC asume que la BBDD no 
176
    // tiene soporte para funciones espaciales.
177
    return false;
178
  }
179

    
180
  @Override
181
  public boolean allowNestedOperations() {
182
    return false;
183
  }
184

    
185
  @Override
186
  public boolean canWriteGeometry(int geometryType, int geometrySubtype) {
187
    // Como va a guardar las geometrias en WKT, puede escribirlas todas.
188
    return true;
189
  }
190

    
191
  @Override
192
  public JDBCSQLBuilderBase createSQLBuilder() {
193
    return new JDBCSQLBuilderBase(this);
194
  }
195

    
196
  @Override
197
  public String getQuoteForIdentifiers() {
198
    return QUOTE_FOR_USE_IN_IDENTIFIERS;
199
  }
200

    
201
  @Override
202
  public boolean allowAutomaticValues() {
203
    return ALLOW_AUTOMATIC_VALUES;
204
  }
205

    
206
  @Override
207
  public boolean supportOffsetInSelect() {
208
    // Asumimos que la BBDD soporta OFFSET
209
    return true;
210
  }
211

    
212
  @Override
213
  public String getQuoteForStrings() {
214
    return QUOTE_FOR_USE_IN_STRINGS;
215
  }
216

    
217
  protected boolean supportCaller(Code.Callable caller) {
218
    if (StringUtils.equalsIgnoreCase(caller.name(), "FOREING_VALUE")) {
219
      return true;
220
    }
221
    Function function = caller.function();
222
    if (function == null) {
223
      return false;
224
    }
225
    if (!function.isSQLCompatible()) {
226
      return false;
227
    }
228
    if (!this.hasSpatialFunctions()) {
229
      if (StringUtils.equalsIgnoreCase(function.group(), Function.GROUP_OGC)) {
230
        return false;
231
      }
232
    }
233
    return true;
234
  }
235

    
236
  @Override
237
  public boolean supportFilter(final FeatureType type, Evaluator filter) {
238
    // No podemos filtrar cuando:
239
    // - Hay una subquery especificada en los parametros 
240
    // - Si hay un filtro y el getSQL devuelbe null.
241
    // - Si se esta usando alguna funcion no-sql en el getSQL
242
    // - Si se estan usando funciones OGC y no soportamos funciones espaciales
243
    // - Si se esta usando algun campo calculado en la expresion de filtro.
244
    // 
245
    // Un proveedor especifico podria sobreescribir el metodo,
246
    // para hilar mas fino al comprobar si soporta el filtro o no.
247
    //
248

    
249
    if (this.useSubquery()) {
250
      // Se esta usando una subquery en los parametros de acceso a la
251
      // BBDD, asi que no podemos filtrar.
252
      return false;
253
    }
254
    if (filter == null) {
255
      // No hay que filtrar nada, asi que se p?ede.
256
      return true;
257
    }
258
    String sql = filter.getSQL();
259
    if (StringUtils.isEmpty(sql)) {
260
      // Hay un filtro, pero la SQL es null, con lo que no podemos
261
      // filtrar por el.
262
      return false;
263
    }
264

    
265
    // Ahora vamos a comprobar que las funciones que se usan son 
266
    // compatibles sql, y que no se usan funciones OGC si el 
267
    // proveedor dice que no soporta funciones espaciales.
268
    // Tambien comprobaremos que el filtro no usa ningun campo calculado.
269
    final MutableBoolean isCompatible = new MutableBoolean(true);
270
    ExpressionEvaluatorManager manager = ExpressionEvaluatorLocator.getManager();
271
    Code code = manager.compile(sql);
272
    SymbolTable symbolTable = manager.createSymbolTable();
273
    code.link(symbolTable);
274
    try {
275
      code.accept(new Visitor() {
276
        @Override
277
        public void visit(Object code_obj) throws VisitCanceledException, BaseException {
278
          Code code = (Code) code_obj;
279
          switch (code.code()) {
280
            case Code.CALLABLE:
281
              Code.Callable caller = (Code.Callable) code;
282
              if (!supportCaller(caller)) {
283
                isCompatible.setValue(false);
284
                throw new VisitCanceledException();
285
              }
286
              break;
287

    
288
            case Code.IDENTIFIER:
289
              Code.Identifier identifier = (Code.Identifier) code;
290
              if (type != null) {
291
                FeatureAttributeDescriptor attrdesc = type.getAttributeDescriptor(identifier.name());
292
                if (attrdesc != null) {
293
                  if (attrdesc.isComputed()) {
294
                    isCompatible.setValue(false);
295
                    throw new VisitCanceledException();
296
                  }
297
                }
298
              }
299
              break;
300
          }
301
        }
302
      });
303

    
304
    } catch (VisitCanceledException ex) {
305
      // Do nothing
306
    } catch (Exception ex) {
307
      LOGGER.warn("Can't calculate if is SQL compatible.", ex);
308
    }
309

    
310
    return isCompatible.booleanValue();
311
  }
312

    
313
  @Override
314
  public boolean supportOrder(FeatureType type, FeatureQueryOrder order) {
315
    if (this.useSubquery()) {
316
      return false;
317
    }
318
    if (order == null) {
319
      return true;
320
    }
321
    for (FeatureQueryOrder.FeatureQueryOrderMember member : order.members()) {
322
      if (member.hasEvaluator()) {
323
        if (!this.supportFilter(type, member.getEvaluator())) {
324
          return false;
325
        }
326
      }
327
    }
328
    return true;
329
  }
330

    
331
  @Override
332
  public OperationsFactory getOperations() {
333
    if (this.operationsFactory == null && !isClosed()) {
334
      this.operationsFactory = new OperationsFactoryBase(this);
335
    }
336
    return operationsFactory;
337
  }
338

    
339
  protected void initializeResource(JDBCConnectionParameters params) {
340
//        Object[] resourceParams = new Object[]{
341
//            params.getUrl(),
342
//            params.getHost(),
343
//            params.getPort(),
344
//            params.getDBName(),
345
//            params.getUser(),
346
//            params.getPassword(),
347
//            params.getJDBCDriverClassName()
348
//        };
349
//
350
//        try {
351
//            ResourceManagerProviderServices manager
352
//                    = (ResourceManagerProviderServices) DALLocator.getResourceManager();
353
//            JDBCResource resource = (JDBCResource) manager.createAddResource(
354
//                    this.getResourceType(),
355
//                    resourceParams
356
//            );
357
//            this.resource = resource;
358
//            this.resource.addConsumer(this);
359
//        } catch (InitializeException ex) {
360
//            logger.trace("Can't initialize resource (" + ArrayUtils.toString(resourceParams) + ").", ex);
361
//            throw new RuntimeException("Can't initialize resource (" + ArrayUtils.toString(resourceParams) + ").", ex);
362
//        }
363

    
364
  }
365

    
366
  @Override
367
  public String getSourceId() {
368
    return this.store.getSourceId();
369
  }
370

    
371
  @Override
372
  public JDBCResource getResource() {
373
    return null;
374
//        return this.resource;
375
  }
376

    
377
  @Override
378
  public Connection getConnection() throws AccessResourceException {
379
    throw new NotYetImplemented();
380
  }
381

    
382
  @Override
383
  public Connection getConnectionWritable() throws AccessResourceException {
384
    return this.getConnection();
385
  }
386

    
387
  @Override
388
  public String getConnectionURL() {
389
    return null;
390
  }
391

    
392
  @Override
393
  public JDBCConnectionParameters getConnectionParameters() {
394
    return connectionParameters;
395
  }
396

    
397
  @Override
398
  public void closeConnection(Connection connection) {
399
      JDBCUtils.close(connection);
400
  }
401

    
402
  @Override
403
  public void closeConnectionQuietly(Connection connection) {
404
      JDBCUtils.closeQuietly(connection);
405
  }
406

    
407
  @Override
408
  protected void doDispose() throws BaseException {
409
    JDBCUtils.closeQuietly(this);
410
  }
411

    
412
  @Override
413
  public void close() throws Exception {
414
    JDBCUtils.closeQuietly(this.resulSetControler);
415
    this.resulSetControler = null;
416
    this.operationsFactory = null;
417
    this.srssolver = null;
418
    this.connectionParameters = null;
419
    this.helperClient = null;
420
    this.geometryManager = null;
421
    this.providerFeatureType = null;
422
    this.store = null;
423
  }
424
  
425
  public boolean isClosed() {
426
      return this.srssolver == null;
427
  }
428

    
429
  @Override
430
  public boolean closeResourceRequested(ResourceProvider resource) {
431
    return this.helperClient.closeResourceRequested(resource);
432
  }
433

    
434
  @Override
435
  public void resourceChanged(ResourceProvider resource) {
436
    this.helperClient.resourceChanged(resource);
437
  }
438

    
439
  @Override
440
  public GeometryManager getGeometryManager() {
441
    if (this.geometryManager == null) {
442
      this.geometryManager = GeometryLocator.getGeometryManager();
443
    }
444
    return this.geometryManager;
445
  }
446

    
447
  @Override
448
  public ResulSetControler getResulSetControler() {
449
    if (this.resulSetControler == null) {
450
      this.resulSetControler = new ResulSetControlerBase(this);
451
    }
452
    return this.resulSetControler;
453
  }
454

    
455
  @Override
456
  public void fetchFeature(FeatureProvider feature, ResultSetEntry rs) throws DataException {
457
    fetchFeature(feature, rs.get(), rs.getColumns(), rs.getExtraValueNames());
458
  }
459

    
460
  @Override
461
  public void fetchFeature(FeatureProvider feature, ResultSet rs, FeatureAttributeDescriptor[] columns, String[] extraValueNames) throws DataException {
462
    Object value;
463
    try {
464
      int rsIndex = 1;
465
      for (FeatureAttributeDescriptor column : columns) {
466
        switch (column.getType()) {
467
          case DataTypes.GEOMETRY:
468
            value = this.getGeometryFromColumn(rs, rsIndex++);
469
            break;
470
          default:
471
            value = rs.getObject(rsIndex++);
472
            if (value instanceof Blob) {
473
              Blob blob = (Blob) value;
474
              value = blob.getBytes(1, (int) blob.length());
475
              blob.free();
476
            } else if(value instanceof Clob) {
477
              Clob clob = (Clob) value;
478
              value = new String(IOUtils.toCharArray(clob.getCharacterStream()));
479
              clob.free();
480
          }
481
        }
482
        feature.set(column.getIndex(), value);
483
      }
484
      if (ArrayUtils.isNotEmpty(extraValueNames)) {
485
        feature.setExtraValueNames(extraValueNames);
486
        for (int index = 0; index < extraValueNames.length; index++) {
487
          value = rs.getObject(rsIndex++);
488
          if (value instanceof Blob) {
489
            Blob blob = (Blob) value;
490
            value = blob.getBytes(0, (int) blob.length());
491
            blob.free();
492
          }
493
          feature.setExtraValue(index, value);
494
        }
495
      }
496
    } catch (Exception ex) {
497
      throw new JDBCCantFetchValueException(ex);
498
    }
499
  }
500

    
501
  @Override
502
  public Geometry getGeometryFromColumn(ResultSetEntry rs, int index) throws DataException {
503
    return getGeometryFromColumn(rs.get(), index);
504
  }
505

    
506
  @Override
507
  public Geometry getGeometryFromColumn(ResultSet rs, int index) throws DataException {
508
    try {
509
      Object value;
510
      switch (this.getGeometrySupportType()) {
511
        case NATIVE:
512
        case WKB:
513
          value = rs.getBytes(index);
514
          if (value == null) {
515
            return null;
516
          }
517
          return this.getGeometryManager().createFrom((byte[]) value);
518

    
519
        case EWKB:
520
          value = rs.getBytes(index);
521
          if (value == null) {
522
            return null;
523
          }
524
          return this.getGeometryManager().createFrom((byte[]) value);
525
        case WKT:
526
        default:
527
          value = rs.getString(index);
528
          if (value == null) {
529
            return null;
530
          }
531
          return this.getGeometryManager().createFrom((String) value);
532

    
533
      }
534
    } catch (Exception ex) {
535
      throw new JDBCCantFetchValueException(ex);
536
    }
537
  }
538

    
539
  @Override
540
  public FeatureProvider createFeature(FeatureType featureType) throws DataException {
541
    return this.store.getStoreServices().createDefaultFeatureProvider(featureType);
542
  }
543

    
544
  @Override
545
  public boolean useSubquery() {
546
    if (this.store == null) {
547
      return false;
548
    }
549
    return !StringUtils.isEmpty(this.store.getParameters().getSQL());
550
  }
551

    
552
  @Override
553
  public SRSSolver getSRSSolver() {
554
    return this.srssolver;
555
  }
556

    
557
  @Override
558
  public JDBCStoreProvider createProvider(
559
          JDBCStoreParameters parameters,
560
          DataStoreProviderServices providerServices
561
  ) throws InitializeException {
562

    
563
    JDBCStoreProviderBase theStore = new JDBCStoreProviderBase(
564
            parameters,
565
            providerServices,
566
            DBHelper.newMetadataContainer(JDBCLibrary.NAME),
567
            this
568
    );
569
    this.initialize(theStore, parameters, theStore);
570
    return theStore;
571
  }
572

    
573
  @Override
574
  public JDBCServerExplorer createServerExplorer(
575
          JDBCServerExplorerParameters parameters,
576
          DataServerExplorerProviderServices providerServices
577
  ) throws InitializeException {
578

    
579
    JDBCServerExplorer explorer = new JDBCServerExplorerBase(
580
            parameters,
581
            providerServices,
582
            this
583
    );
584
    this.initialize(explorer, parameters, null);
585
    return explorer;
586
  }
587

    
588
  @Override
589
  public JDBCNewStoreParameters createNewStoreParameters() {
590
    return new JDBCNewStoreParameters();
591
  }
592

    
593
  @Override
594
  public JDBCStoreParameters createOpenStoreParameters() {
595
    return new JDBCStoreParameters();
596
  }
597
  
598
  @Override
599
  public JDBCStoreParameters createOpenStoreParameters(JDBCServerExplorerParameters parameters) {
600
    JDBCStoreParameters params = this.createOpenStoreParameters();
601
    params.setHost(parameters.getHost());
602
    params.setPort(parameters.getPort());
603
    params.setDBName(parameters.getDBName());
604
    params.setUser(parameters.getUser());
605
    params.setPassword(parameters.getPassword());
606
    params.setCatalog(parameters.getCatalog());
607
    params.setSchema(parameters.getSchema());
608
    params.setJDBCDriverClassName(parameters.getJDBCDriverClassName());
609
    params.setUrl(parameters.getUrl());
610
    if( parameters instanceof FilesystemStoreParameters ) {
611
        File f = ((FilesystemStoreParameters) parameters).getFile();
612
        ((FilesystemStoreParameters) params).setFile(f);
613
    }
614
    return params;
615
  }
616
  
617

    
618
  @Override
619
  public JDBCServerExplorerParameters createServerExplorerParameters() {
620
    return new JDBCServerExplorerParameters();
621
  }
622

    
623
  @Override
624
  public String getSourceId(JDBCStoreParameters parameters) {
625
    return parameters.getHost() + ":"
626
            + parameters.getDBName() + ":"
627
            + parameters.getSchema() + ":"
628
            + parameters.tableID();
629
  }
630

    
631
  @Override
632
  public boolean isThreadSafe() {
633
    return true;
634
  }
635

    
636
  /** 
637
   * This method has been overriden in Oracle provider
638
   * 
639
   * @param sqlbuilder
640
   * @param type
641
   * @param extra_column_names 
642
   */
643
  @Override
644
  public void processSpecialFunctions(
645
          SQLBuilder sqlbuilder,
646
          FeatureType type,
647
          List<String> extra_column_names) {
648
    replaceForeingValueFunction(sqlbuilder, type, extra_column_names);
649
    replaceExistsFunction(sqlbuilder, type, extra_column_names);
650
  }
651

    
652
  private void replaceExistsFunction(
653
          SQLBuilder sqlbuilder,
654
          FeatureType type,
655
          final List<String> extra_column_names) {
656
    
657
    //  Si lse encuentra una construccion del tipo:
658
    //    SELECT ... FROM ... WHERE ... EXISTS(list, 'EXISTS_ID') ...
659
    //  se traslada a:
660
    //    SELECT ... EXISTS(list) AS EXISTS_ID FROM ... WHERE ... EXISTS(list) ...
661
    //  Y se a?ade el valor ESISTS_ID a las columnas extra a recuperar de la consulta.
662
    //          
663
    
664
    final SQLBuilder.SelectBuilder select = sqlbuilder.select();
665
    final ExpressionBuilder where = select.where();
666
    if (where == null || where.isEmpty() || select.has_group_by() ) {
667
      return;
668
    }
669
    final List<ExpressionBuilder.Value[]> value_replacements = new ArrayList<>();
670
    where.accept(new ExpressionBuilder.Visitor() {
671
      @Override
672
      public void visit(ExpressionBuilder.Visitable value) {
673
        if (!(value instanceof ExpressionBuilder.Function)) {
674
          return;
675
        }
676
        ExpressionBuilder.Function function = (ExpressionBuilder.Function) value;
677
        if (!StringUtils.equalsIgnoreCase(function.name(), FUNCTION_EXISTS)) {
678
          return;
679
        }
680
        if (function.parameters().size() != 2) {
681
          return;
682
        }
683
        ExpressionBuilder.Value arg0 = function.parameters().get(0);
684
        ExpressionBuilder.Value arg1 = function.parameters().get(1);
685
        if (arg1 == null) {
686
          return;
687
        }
688
        String columnName = (String) ((ExpressionBuilder.Constant) arg1).value();
689
        SQLBuilder.SelectColumnBuilder column = select.column();
690
        column.value(
691
                sqlbuilder.expression().function(FUNCTION_EXISTS, arg0)
692
        );
693
        column.as(columnName);
694

    
695
        if( extra_column_names!=null ) {
696
          extra_column_names.add(columnName);
697
        }
698
      }
699
    }, null);
700
    if (value_replacements.isEmpty()) {
701
      return;
702
    }
703
    // Realizamos los reemplazos calculados previamente (value_replacements).
704
    for (ExpressionBuilder.Value[] replaceValue : value_replacements) {
705
      ExpressionBuilder.Value target = replaceValue[0];
706
      ExpressionBuilder.Value replacement = replaceValue[1];
707
      sqlbuilder.select().replace(target, replacement);
708
    }
709
  }
710

    
711
  protected void replaceForeingValueFunction(
712
          SQLBuilder sqlbuilder,
713
          FeatureType type,
714
          List<String> extra_column_names) {
715
    try {
716
      // See test SQLBuilderTest->testForeingValue()
717
      final ExpressionBuilder where = sqlbuilder.select().where();
718
//      if (where == null || where.isEmpty()) {
719
//        return;
720
//      }
721
      final SQLBuilder.TableNameBuilder table = sqlbuilder.select().from().table();
722
      final ExpressionBuilder expbuilder = sqlbuilder.expression();
723

    
724
      final List<String> foreing_value_args;
725
      if (extra_column_names == null || sqlbuilder.select().has_group_by()) {
726
        foreing_value_args = new ArrayList<>();
727
      } else {
728
        foreing_value_args = extra_column_names;
729
      }
730
      final List<ExpressionBuilder.Value[]> value_replacements = new ArrayList<>();
731

    
732
      // Buscamos las llamadas a la funcion "foreing_value" y nos quedamos
733
      // el argumento de esta asi como por que tendriamos que sustituirla 
734
      // una vez hechos los left joins que toquen.
735
      sqlbuilder.select().accept(new ExpressionBuilder.Visitor() {
736
        @Override
737
        public void visit(ExpressionBuilder.Visitable value) {
738
          // Requiere que sea la funcion "FOREING_VALUE con un solo 
739
          // argumento que sea una constante de tipo string.
740
          if (!(value instanceof ExpressionBuilder.Function)) {
741
            return;
742
          }
743
          ExpressionBuilder.Function function = (ExpressionBuilder.Function) value;
744
          if (!StringUtils.equalsIgnoreCase(function.name(), FUNCTION_FOREING_VALUE)) {
745
            return;
746
          }
747
          if (function.parameters().size() != 1) {
748
            return;
749
          }
750
          ExpressionBuilder.Value arg = function.parameters().get(0);
751
          if (!(arg instanceof ExpressionBuilder.Constant)) {
752
            return;
753
          }
754
          Object arg_value = ((ExpressionBuilder.Constant) arg).value();
755
          if (!(arg_value instanceof CharSequence)) {
756
            return;
757
          }
758
          String foreing_value_arg = arg_value.toString();
759
          String[] foreingNameParts = StringUtils.split(foreing_value_arg, "[.]");
760
          if (foreingNameParts.length != 2) {
761
            // De momento solo tratamos joins entre dos tablas.
762
            return;
763
          }
764
          String columnNameLocal = foreingNameParts[0];
765
          String columnNameForeing = foreingNameParts[1];
766
          FeatureAttributeDescriptor attr = type.getAttributeDescriptor(columnNameLocal);
767
          if (attr==null) {
768
              throw new RuntimeException("Cannot find in feature type attribute:"+columnNameLocal);
769
          }
770
          if (!attr.isForeingKey()) {
771
            // Uhm... si el argumento no referencia a un campo que es
772
            // clave ajena no lo procesamos. 
773
            // ? Deberiamos lanzar un error ?
774
            return;
775
          }
776
          // Nos guardaremos por que hay que reemplazar la funcion 
777
          // FOREING_VALUE, y su argumento para mas tarde.
778
          ForeingKey foreingKey = attr.getForeingKey();
779
          SQLBuilder.TableNameBuilder foreingTable = sqlbuilder.createTableNameBuilder()
780
                  .database(table.getDatabase())
781
                  .schema(table.getSchema())
782
                  .name(foreingKey.getTableName());
783
          // Reemplzaremos la funcion FOREING_VALUE, por el acceso al campo
784
          // que toca de la tabla a la que referencia la clave ajena.
785
          ExpressionBuilder.Variable function_replacement = sqlbuilder.column(foreingTable, columnNameForeing);
786
          value_replacements.add(
787
                  new ExpressionBuilder.Value[]{
788
                    function,
789
                    function_replacement
790
                  }
791
          );
792
          if (!foreing_value_args.contains(foreing_value_arg)) {
793
            foreing_value_args.add(foreing_value_arg);
794
          }
795
        }
796
      }, null);
797
      
798
      // Si no habia ningun llamada a la funcion FOREING_VALUE, no hay que
799
      // hacer nada.
800
      if (foreing_value_args.isEmpty()) {
801
        return;
802
      }
803

    
804
      // Calculamos que referencias de columnas hemos de cambiar para 
805
      // que no aparezcan ambiguedades al hacer el join (nombres de
806
      // columna de una y otra tabla que coincidan).
807
      // Para las columnas que puedan dar conflicto se prepara un reemplazo 
808
      // de estas que tenga el nombre de tabla.
809
      for (ExpressionBuilder.Variable variable : sqlbuilder.variables()) {
810
        if (variable instanceof SQLBuilderBase.ColumnBase) {
811
          continue;
812
        }
813
        for (String foreingName : foreing_value_args) {
814
          String[] foreingNameParts = foreingName.split("[.]");
815
          if (foreingNameParts.length != 2) {
816
            continue;
817
          }
818
          String columnNameLocal = foreingNameParts[0];
819
          String columnNameForeing = foreingNameParts[1];
820
          if (StringUtils.equalsIgnoreCase(variable.name(), columnNameForeing)
821
                  || StringUtils.equalsIgnoreCase(variable.name(), columnNameLocal)) {
822
            ExpressionBuilder.Variable variable_replacement = sqlbuilder.column(
823
                    table,
824
                    variable.name()
825
            );
826
            value_replacements.add(
827
                    new ExpressionBuilder.Value[]{
828
                      variable,
829
                      variable_replacement
830
                    }
831
            );
832
          }
833
        }
834
      }
835

    
836
      // Realizamos los reemplazos calculados previamente (value_replacements).
837
      for (ExpressionBuilder.Value[] replaceValue : value_replacements) {
838
        ExpressionBuilder.Value target = replaceValue[0];
839
        ExpressionBuilder.Value replacement = replaceValue[1];
840
        sqlbuilder.select().replace(target, replacement);
841
      }
842

    
843
        // A?adimos a la SQL los "LEFT JOIN" que toca para poder acceder
844
        // a los valores referenciados por la funcion FOREING_VALUE.
845
        // Ademas a?adimos los valores referenciados por la funcion FOREING_VALUE
846
        // como columnas de la SQL para poder acceder a ellos si fuese necesario.
847
        HashSet usedLeftJoins = new HashSet();
848
      for (String foreingName : foreing_value_args) {
849
        String[] foreingNameParts = foreingName.split("[.]");
850
        if (foreingNameParts.length != 2) {
851
          continue;
852
        }
853
        String columnNameLocal = foreingNameParts[0];
854
        String columnNameForeing = foreingNameParts[1];
855
        FeatureAttributeDescriptor attr = type.getAttributeDescriptor(columnNameLocal);
856
        if (attr.isForeingKey()) {
857
          ForeingKey foreingKey = attr.getForeingKey();
858
          SQLBuilder.TableNameBuilder foreingTable = sqlbuilder.createTableNameBuilder()
859
                  .database(table.getDatabase())
860
                  .schema(table.getSchema())
861
                  .name(foreingKey.getTableName());
862
          SQLBuilder.TableNameBuilder mainTable = sqlbuilder.createTableNameBuilder()
863
                  .database(table.getDatabase())
864
                  .schema(table.getSchema())
865
                  .name(table.getName());
866
          
867
          if (!usedLeftJoins.contains(foreingTable.getName())) {
868
            sqlbuilder.select().from()
869
                    .left_join(
870
                            foreingTable,
871
                            expbuilder.eq(
872
                                    sqlbuilder.column(mainTable, columnNameLocal),
873
                                    sqlbuilder.column(foreingTable, foreingKey.getCodeName())
874
                            )
875
                    );
876
            usedLeftJoins.add(foreingTable.getName());
877
          }
878
          if (!sqlbuilder.select().has_group_by()) {
879
                sqlbuilder.select().column().name(foreingTable, columnNameForeing);
880
          }
881
        }
882
      }
883
      
884
        for (SQLBuilder.SelectColumnBuilder column : sqlbuilder.select().getColumns()) {
885
            if(column.getTable()==null && column.getName()!=null) {
886
                column.name(table, column.getName());
887
            }
888
        }
889

    
890
    } catch (Throwable th) {
891
      LOGGER.warn("Can't replace FOREING_VALUE function.", th);
892
      throw th;
893
    } finally {
894
      LOGGER.trace("Exit from replaceForeingValueFunction.");
895
    }
896
  }
897
}