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

History | View | Annotate | Download (28.5 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.sql.Blob;
27
import java.sql.Clob;
28
import org.gvsig.fmap.dal.store.jdbc2.impl.ResulSetControlerBase;
29
import org.gvsig.fmap.dal.store.jdbc2.spi.operations.OperationsFactoryBase;
30
import java.sql.Connection;
31
import java.sql.ResultSet;
32
import java.util.ArrayList;
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

    
56
import org.gvsig.fmap.dal.feature.spi.FeatureProvider;
57
import org.gvsig.fmap.dal.feature.spi.SQLBuilderBase;
58
import org.gvsig.fmap.dal.resource.exception.AccessResourceException;
59
import org.gvsig.fmap.dal.resource.spi.ResourceConsumer;
60
import org.gvsig.fmap.dal.resource.spi.ResourceProvider;
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.geom.Geometry;
79
import org.gvsig.fmap.geom.GeometryLocator;
80
import org.gvsig.fmap.geom.GeometryManager;
81
import org.gvsig.tools.dispose.impl.AbstractDisposable;
82
import org.gvsig.tools.evaluator.Evaluator;
83
import org.gvsig.tools.exception.BaseException;
84
import org.gvsig.tools.exception.NotYetImplemented;
85
import org.gvsig.tools.visitor.VisitCanceledException;
86
import org.gvsig.tools.visitor.Visitor;
87
import org.slf4j.Logger;
88
import org.slf4j.LoggerFactory;
89

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

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

    
97
  private static final Logger LOGGER = LoggerFactory.getLogger(JDBCHelperBase.class);
98

    
99
  private ResulSetControler resulSetControler = null;
100

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

    
105
  private GeometryManager geometryManager = null;
106

    
107
  private JDBCConnectionParameters connectionParameters;
108

    
109
  private JDBCStoreProvider store;
110

    
111
  protected OperationsFactory operationsFactory = null;
112

    
113
  protected SRSSolver srssolver;
114
  
115
  protected FeatureType providerFeatureType = null;
116

    
117
  public JDBCHelperBase(JDBCConnectionParameters connectionParameters) {
118
    this.connectionParameters = connectionParameters;
119

    
120
    // If a particular treatment is required to convert SRS to the 
121
    // BBDD format, overwrite JDBCSRSsBase and use it instead of JDBCSRSsDumb.
122
    this.srssolver = new SRSSolverDumb(this);
123
  }
124

    
125
  protected void initialize(
126
          ResourceConsumer helperClient,
127
          JDBCConnectionParameters connectionParameters,
128
          JDBCStoreProvider store
129
  ) {
130
    this.store = store;
131
    this.helperClient = helperClient;
132
    this.connectionParameters = connectionParameters;
133
    initializeResource(connectionParameters);
134
  }
135

    
136
  protected String getResourceType() {
137
    return JDBCResource.NAME;
138
  }
139

    
140
  @Override
141
  public String getProviderName() {
142
    return JDBCLibrary.NAME;
143
  }
144

    
145
  @Override
146
  public GeometrySupportType getGeometrySupportType() {
147
    // El proveedor generico de JDBC guadara las geoemtrias como WKT
148
    return GeometrySupportType.WKT;
149
  }
150
  
151
    /**
152
     * @return the providerFeatureType
153
     */
154
  @Override
155
    public FeatureType getProviderFeatureType() {
156
        return providerFeatureType;
157
    }
158

    
159
    /**
160
     * @param providerFeatureType the providerFeatureType to set
161
     */
162
  @Override
163
    public void setProviderFeatureType(FeatureType providerFeatureType) {
164
        this.providerFeatureType = providerFeatureType;
165
    }
166

    
167

    
168
  @Override
169
  public boolean hasSpatialFunctions() {
170
    // Por defecto el proveedor generico de JDBC asume que la BBDD no 
171
    // tiene soporte para funciones espaciales.
172
    return false;
173
  }
174

    
175
  @Override
176
  public boolean allowNestedOperations() {
177
    return false;
178
  }
179

    
180
  @Override
181
  public boolean canWriteGeometry(int geometryType, int geometrySubtype) {
182
    // Como va a guardar las geometrias en WKT, puede escribirlas todas.
183
    return true;
184
  }
185

    
186
  @Override
187
  public JDBCSQLBuilderBase createSQLBuilder() {
188
    return new JDBCSQLBuilderBase(this);
189
  }
190

    
191
  @Override
192
  public String getQuoteForIdentifiers() {
193
    return QUOTE_FOR_USE_IN_IDENTIFIERS;
194
  }
195

    
196
  @Override
197
  public boolean allowAutomaticValues() {
198
    return ALLOW_AUTOMATIC_VALUES;
199
  }
200

    
201
  @Override
202
  public boolean supportOffsetInSelect() {
203
    // Asumimos que la BBDD soporta OFFSET
204
    return true;
205
  }
206

    
207
  @Override
208
  public String getQuoteForStrings() {
209
    return QUOTE_FOR_USE_IN_STRINGS;
210
  }
211

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

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

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

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

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

    
299
    } catch (VisitCanceledException ex) {
300
      // Do nothing
301
    } catch (Exception ex) {
302
      LOGGER.warn("Can't calculate if is SQL compatible.", ex);
303
    }
304

    
305
    return isCompatible.booleanValue();
306
  }
307

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

    
326
  @Override
327
  public OperationsFactory getOperations() {
328
    if (this.operationsFactory == null) {
329
      this.operationsFactory = new OperationsFactoryBase(this);
330
    }
331
    return operationsFactory;
332
  }
333

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

    
359
  }
360

    
361
  @Override
362
  public String getSourceId() {
363
    return this.store.getSourceId();
364
  }
365

    
366
  @Override
367
  public JDBCResource getResource() {
368
    return null;
369
//        return this.resource;
370
  }
371

    
372
  @Override
373
  public Connection getConnection() throws AccessResourceException {
374
    throw new NotYetImplemented();
375
  }
376

    
377
  @Override
378
  public Connection getConnectionWritable() throws AccessResourceException {
379
    return this.getConnection();
380
  }
381

    
382
  @Override
383
  public String getConnectionURL() {
384
    return null;
385
  }
386

    
387
  @Override
388
  public JDBCConnectionParameters getConnectionParameters() {
389
    return connectionParameters;
390
  }
391

    
392
  @Override
393
  public void closeConnection(Connection connection) {
394
      JDBCUtils.close(connection);
395
  }
396

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

    
402
  @Override
403
  protected void doDispose() throws BaseException {
404
    JDBCUtils.closeQuietly(this);
405
  }
406

    
407
  @Override
408
  public void close() throws Exception {
409
//        this.resource.removeConsumer(this);
410
    JDBCUtils.closeQuietly(this.resulSetControler);
411
//        this.resource = null;
412
    this.resulSetControler = null;
413
  }
414

    
415
  @Override
416
  public boolean closeResourceRequested(ResourceProvider resource) {
417
    return this.helperClient.closeResourceRequested(resource);
418
  }
419

    
420
  @Override
421
  public void resourceChanged(ResourceProvider resource) {
422
    this.helperClient.resourceChanged(resource);
423
  }
424

    
425
  @Override
426
  public GeometryManager getGeometryManager() {
427
    if (this.geometryManager == null) {
428
      this.geometryManager = GeometryLocator.getGeometryManager();
429
    }
430
    return this.geometryManager;
431
  }
432

    
433
  @Override
434
  public ResulSetControler getResulSetControler() {
435
    if (this.resulSetControler == null) {
436
      this.resulSetControler = new ResulSetControlerBase(this);
437
    }
438
    return this.resulSetControler;
439
  }
440

    
441
  @Override
442
  public void fetchFeature(FeatureProvider feature, ResultSetEntry rs) throws DataException {
443
    fetchFeature(feature, rs.get(), rs.getColumns(), rs.getExtraValueNames());
444
  }
445

    
446
  @Override
447
  public void fetchFeature(FeatureProvider feature, ResultSet rs, FeatureAttributeDescriptor[] columns, String[] extraValueNames) throws DataException {
448
    Object value;
449
    try {
450
      int rsIndex = 1;
451
      for (FeatureAttributeDescriptor column : columns) {
452
        switch (column.getType()) {
453
          case DataTypes.GEOMETRY:
454
            value = this.getGeometryFromColumn(rs, rsIndex++);
455
            break;
456
          default:
457
            value = rs.getObject(rsIndex++);
458
            if (value instanceof Blob) {
459
              Blob blob = (Blob) value;
460
              value = blob.getBytes(1, (int) blob.length());
461
              blob.free();
462
            } else if(value instanceof Clob) {
463
              Clob clob = (Clob) value;
464
              value = new String(IOUtils.toCharArray(clob.getCharacterStream()));
465
              clob.free();
466
          }
467
        }
468
        feature.set(column.getIndex(), value);
469
      }
470
      if (ArrayUtils.isNotEmpty(extraValueNames)) {
471
        feature.setExtraValueNames(extraValueNames);
472
        for (int index = 0; index < extraValueNames.length; index++) {
473
          value = rs.getObject(rsIndex++);
474
          if (value instanceof Blob) {
475
            Blob blob = (Blob) value;
476
            value = blob.getBytes(0, (int) blob.length());
477
            blob.free();
478
          }
479
          feature.setExtraValue(index, value);
480
        }
481
      }
482
    } catch (Exception ex) {
483
      throw new JDBCCantFetchValueException(ex);
484
    }
485
  }
486

    
487
  @Override
488
  public Geometry getGeometryFromColumn(ResultSetEntry rs, int index) throws DataException {
489
    return getGeometryFromColumn(rs.get(), index);
490
  }
491

    
492
  @Override
493
  public Geometry getGeometryFromColumn(ResultSet rs, int index) throws DataException {
494
    try {
495
      Object value;
496
      switch (this.getGeometrySupportType()) {
497
        case NATIVE:
498
        case WKB:
499
          value = rs.getBytes(index);
500
          if (value == null) {
501
            return null;
502
          }
503
          return this.getGeometryManager().createFrom((byte[]) value);
504

    
505
        case EWKB:
506
          value = rs.getBytes(index);
507
          if (value == null) {
508
            return null;
509
          }
510
          return this.getGeometryManager().createFrom((byte[]) value);
511
        case WKT:
512
        default:
513
          value = rs.getString(index);
514
          if (value == null) {
515
            return null;
516
          }
517
          return this.getGeometryManager().createFrom((String) value);
518

    
519
      }
520
    } catch (Exception ex) {
521
      throw new JDBCCantFetchValueException(ex);
522
    }
523
  }
524

    
525
  @Override
526
  public FeatureProvider createFeature(FeatureType featureType) throws DataException {
527
    return this.store.getStoreServices().createDefaultFeatureProvider(featureType);
528
  }
529

    
530
  @Override
531
  public boolean useSubquery() {
532
    if (this.store == null) {
533
      return false;
534
    }
535
    return !StringUtils.isEmpty(this.store.getParameters().getSQL());
536
  }
537

    
538
  @Override
539
  public SRSSolver getSRSSolver() {
540
    return this.srssolver;
541
  }
542

    
543
  @Override
544
  public JDBCStoreProvider createProvider(
545
          JDBCStoreParameters parameters,
546
          DataStoreProviderServices providerServices
547
  ) throws InitializeException {
548

    
549
    JDBCStoreProviderBase theStore = new JDBCStoreProviderBase(
550
            parameters,
551
            providerServices,
552
            DBHelper.newMetadataContainer(JDBCLibrary.NAME),
553
            this
554
    );
555
    this.initialize(theStore, parameters, theStore);
556
    return theStore;
557
  }
558

    
559
  @Override
560
  public JDBCServerExplorer createServerExplorer(
561
          JDBCServerExplorerParameters parameters,
562
          DataServerExplorerProviderServices providerServices
563
  ) throws InitializeException {
564

    
565
    JDBCServerExplorer explorer = new JDBCServerExplorerBase(
566
            parameters,
567
            providerServices,
568
            this
569
    );
570
    this.initialize(explorer, parameters, null);
571
    return explorer;
572
  }
573

    
574
  @Override
575
  public JDBCNewStoreParameters createNewStoreParameters() {
576
    return new JDBCNewStoreParameters();
577
  }
578

    
579
  @Override
580
  public JDBCStoreParameters createOpenStoreParameters() {
581
    return new JDBCStoreParameters();
582
  }
583

    
584
  @Override
585
  public JDBCServerExplorerParameters createServerExplorerParameters() {
586
    return new JDBCServerExplorerParameters();
587
  }
588

    
589
  @Override
590
  public String getSourceId(JDBCStoreParameters parameters) {
591
    return parameters.getHost() + ":"
592
            + parameters.getDBName() + ":"
593
            + parameters.getSchema() + ":"
594
            + parameters.tableID();
595
  }
596

    
597
  @Override
598
  public boolean isThreadSafe() {
599
    return true;
600
  }
601

    
602
  /** 
603
   * This method has been overriden in Oracle provider
604
   * 
605
   * @param sqlbuilder
606
   * @param type
607
   * @param extra_column_names 
608
   */
609
  @Override
610
  public void processSpecialFunctions(
611
          SQLBuilder sqlbuilder,
612
          FeatureType type,
613
          List<String> extra_column_names) {
614
    replaceForeingValueFunction(sqlbuilder, type, extra_column_names);
615
    replaceExistsFunction(sqlbuilder, type, extra_column_names);
616
  }
617

    
618
  private void replaceExistsFunction(
619
          SQLBuilder sqlbuilder,
620
          FeatureType type,
621
          final List<String> extra_column_names) {
622
    
623
    //  Si lse encuentra una construccion del tipo:
624
    //    SELECT ... FROM ... WHERE ... EXISTS(list, 'EXISTS_ID') ...
625
    //  se traslada a:
626
    //    SELECT ... EXISTS(list) AS EXISTS_ID FROM ... WHERE ... EXISTS(list) ...
627
    //  Y se a?ade el valor ESISTS_ID a las columnas extra a recuperar de la consulta.
628
    //          
629
    
630
    final SQLBuilder.SelectBuilder select = sqlbuilder.select();
631
    final ExpressionBuilder where = select.where();
632
    if (where == null || where.isEmpty() || select.has_group_by() ) {
633
      return;
634
    }
635
    final List<ExpressionBuilder.Value[]> value_replacements = new ArrayList<>();
636
    where.accept(new ExpressionBuilder.Visitor() {
637
      @Override
638
      public void visit(ExpressionBuilder.Visitable value) {
639
        if (!(value instanceof ExpressionBuilder.Function)) {
640
          return;
641
        }
642
        ExpressionBuilder.Function function = (ExpressionBuilder.Function) value;
643
        if (!StringUtils.equalsIgnoreCase(function.name(), FUNCTION_EXISTS)) {
644
          return;
645
        }
646
        if (function.parameters().size() != 2) {
647
          return;
648
        }
649
        ExpressionBuilder.Value arg0 = function.parameters().get(0);
650
        ExpressionBuilder.Value arg1 = function.parameters().get(1);
651
        if (arg1 == null) {
652
          return;
653
        }
654
        String columnName = (String) ((ExpressionBuilder.Constant) arg1).value();
655
        SQLBuilder.SelectColumnBuilder column = select.column();
656
        column.value(
657
                sqlbuilder.expression().function(FUNCTION_EXISTS, arg0)
658
        );
659
        column.as(columnName);
660

    
661
        if( extra_column_names!=null ) {
662
          extra_column_names.add(columnName);
663
        }
664
      }
665
    }, null);
666
    if (value_replacements.isEmpty()) {
667
      return;
668
    }
669
    // Realizamos los reemplazos calculados previamente (value_replacements).
670
    for (ExpressionBuilder.Value[] replaceValue : value_replacements) {
671
      ExpressionBuilder.Value target = replaceValue[0];
672
      ExpressionBuilder.Value replacement = replaceValue[1];
673
      sqlbuilder.select().replace(target, replacement);
674
    }
675
  }
676

    
677
  protected void replaceForeingValueFunction(
678
          SQLBuilder sqlbuilder,
679
          FeatureType type,
680
          List<String> extra_column_names) {
681
    try {
682
      // See test SQLBuilderTest->testForeingValue()
683
      final ExpressionBuilder where = sqlbuilder.select().where();
684
      if (where == null || where.isEmpty()) {
685
        return;
686
      }
687
      final SQLBuilder.TableNameBuilder table = sqlbuilder.select().from().table();
688
      final ExpressionBuilder expbuilder = sqlbuilder.expression();
689

    
690
      final List<String> foreing_value_args;
691
      if (extra_column_names == null) {
692
        foreing_value_args = new ArrayList<>();
693
      } else {
694
        foreing_value_args = extra_column_names;
695
      }
696
      final List<ExpressionBuilder.Value[]> value_replacements = new ArrayList<>();
697

    
698
      // Buscamos las llamadas a la funcion "foreing_value" y nos quedamos
699
      // el argumento de esta asi como por que tendriamos que sustituirla 
700
      // una vez hechos los left joins que toquen.
701
      where.accept(new ExpressionBuilder.Visitor() {
702
        @Override
703
        public void visit(ExpressionBuilder.Visitable value) {
704
          // Requiere que sea la funcion "FOREING_VALUE con un solo 
705
          // argumento que sea una constante de tipo string.
706
          if (!(value instanceof ExpressionBuilder.Function)) {
707
            return;
708
          }
709
          ExpressionBuilder.Function function = (ExpressionBuilder.Function) value;
710
          if (!StringUtils.equalsIgnoreCase(function.name(), FUNCTION_FOREING_VALUE)) {
711
            return;
712
          }
713
          if (function.parameters().size() != 1) {
714
            return;
715
          }
716
          ExpressionBuilder.Value arg = function.parameters().get(0);
717
          if (!(arg instanceof ExpressionBuilder.Constant)) {
718
            return;
719
          }
720
          Object arg_value = ((ExpressionBuilder.Constant) arg).value();
721
          if (!(arg_value instanceof CharSequence)) {
722
            return;
723
          }
724
          String foreing_value_arg = arg_value.toString();
725
          String[] foreingNameParts = StringUtils.split(foreing_value_arg, "[.]");
726
          if (foreingNameParts.length != 2) {
727
            // De momento solo tratamos joins entre dos tablas.
728
            return;
729
          }
730
          String columnNameLocal = foreingNameParts[0];
731
          String columnNameForeing = foreingNameParts[1];
732
          FeatureAttributeDescriptor attr = type.getAttributeDescriptor(columnNameLocal);
733
          if (!attr.isForeingKey()) {
734
            // Uhm... si el argumento no referencia a un campo que es
735
            // clave ajena no lo procesamos. 
736
            // ? Deberiamos lanzar un error ?
737
            return;
738
          }
739
          // Nos guardaremos por que hay que reemplazar la funcion 
740
          // FOREING_VALUE, y su argumento para mas tarde.
741
          ForeingKey foreingKey = attr.getForeingKey();
742
          SQLBuilder.TableNameBuilder foreingTable = sqlbuilder.createTableNameBuilder()
743
                  .database(table.getDatabase())
744
                  .schema(table.getSchema())
745
                  .name(foreingKey.getTableName());
746
          // Reemplzaremos la funcion FOREING_VALUE, por el acceso al campo
747
          // que toca de la tabla a la que referencia la clave ajena.
748
          ExpressionBuilder.Variable function_replacement = sqlbuilder.column(foreingTable, columnNameForeing);
749
          value_replacements.add(
750
                  new ExpressionBuilder.Value[]{
751
                    function,
752
                    function_replacement
753
                  }
754
          );
755
          foreing_value_args.add(foreing_value_arg);
756
        }
757
      }, null);
758

    
759
      // Si no habia ningun llamada a la funcion FOREING_VALUE, no hay que
760
      // hacer nada.
761
      if (foreing_value_args.isEmpty()) {
762
        return;
763
      }
764

    
765
      // Calculamos que referencias de columnas hemos de cambiar para 
766
      // que no aparezcan ambiguedades al hacer el join (nombres de
767
      // columna de una y otra tabla que coincidan).
768
      // Para las columnas que puedan dar conflicto se prepara un reemplazo 
769
      // de estas que tenga el nombre de tabla.
770
      for (ExpressionBuilder.Variable variable : sqlbuilder.variables()) {
771
        if (variable instanceof SQLBuilderBase.ColumnBase) {
772
          continue;
773
        }
774
        for (String foreingName : foreing_value_args) {
775
          String[] foreingNameParts = foreingName.split("[.]");
776
          if (foreingNameParts.length != 2) {
777
            continue;
778
          }
779
          String columnNameLocal = foreingNameParts[0];
780
          String columnNameForeing = foreingNameParts[1];
781
          if (StringUtils.equalsIgnoreCase(variable.name(), columnNameForeing)
782
                  || StringUtils.equalsIgnoreCase(variable.name(), columnNameLocal)) {
783
            ExpressionBuilder.Variable variable_replacement = sqlbuilder.column(
784
                    table,
785
                    variable.name()
786
            );
787
            value_replacements.add(
788
                    new ExpressionBuilder.Value[]{
789
                      variable,
790
                      variable_replacement
791
                    }
792
            );
793
          }
794
        }
795
      }
796

    
797
      // Realizamos los reemplazos calculados previamente (value_replacements).
798
      for (ExpressionBuilder.Value[] replaceValue : value_replacements) {
799
        ExpressionBuilder.Value target = replaceValue[0];
800
        ExpressionBuilder.Value replacement = replaceValue[1];
801
        sqlbuilder.select().replace(target, replacement);
802
      }
803

    
804
      // A?adimos a la SQL los "LEFT JOIN" que toca para poder acceder
805
      // a los valores referenciados por la funcion FOREING_VALUE.
806
      // Ademas a?adimos los valores referenciados por la funcion FOREING_VALUE
807
      // como columnas de la SQL para poder acceder a ellos si fuese necesario.
808
      for (String foreingName : foreing_value_args) {
809
        String[] foreingNameParts = foreingName.split("[.]");
810
        if (foreingNameParts.length != 2) {
811
          continue;
812
        }
813
        String columnNameLocal = foreingNameParts[0];
814
        String columnNameForeing = foreingNameParts[1];
815
        FeatureAttributeDescriptor attr = type.getAttributeDescriptor(columnNameLocal);
816
        if (attr.isForeingKey()) {
817
          ForeingKey foreingKey = attr.getForeingKey();
818
          SQLBuilder.TableNameBuilder foreingTable = sqlbuilder.createTableNameBuilder()
819
                  .database(table.getDatabase())
820
                  .schema(table.getSchema())
821
                  .name(foreingKey.getTableName());
822
          SQLBuilder.TableNameBuilder mainTable = sqlbuilder.createTableNameBuilder()
823
                  .database(table.getDatabase())
824
                  .schema(table.getSchema())
825
                  .name(table.getName());
826

    
827
          sqlbuilder.select().from()
828
                  .left_join(
829
                          foreingTable,
830
                          expbuilder.eq(
831
                                  sqlbuilder.column(mainTable, columnNameLocal),
832
                                  sqlbuilder.column(foreingTable, foreingKey.getCodeName())
833
                          )
834
                  );
835
          sqlbuilder.select().column().name(foreingTable, columnNameForeing);
836
        }
837
      }
838

    
839
    } catch (Throwable th) {
840
      LOGGER.warn("Can't replace FORENG_VALUE function.", th);
841
      throw th;
842
    } finally {
843
      LOGGER.trace("Exit from replaceForeingValueFunction.");
844
    }
845
  }
846
}