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

History | View | Annotate | Download (29 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.HashSet;
34
import java.util.List;
35
import org.apache.commons.io.IOUtils;
36
import org.apache.commons.lang3.ArrayUtils;
37
import org.apache.commons.lang3.StringUtils;
38
import org.apache.commons.lang3.mutable.MutableBoolean;
39
import org.gvsig.expressionevaluator.Code;
40
import org.gvsig.expressionevaluator.ExpressionBuilder;
41
import org.gvsig.expressionevaluator.ExpressionEvaluatorLocator;
42
import org.gvsig.expressionevaluator.ExpressionEvaluatorManager;
43
import org.gvsig.expressionevaluator.Function;
44
import org.gvsig.expressionevaluator.GeometryExpressionBuilderHelper.GeometrySupportType;
45
import org.gvsig.expressionevaluator.SymbolTable;
46
import static org.gvsig.fmap.dal.DataManager.FUNCTION_EXISTS;
47
import static org.gvsig.fmap.dal.DataManager.FUNCTION_FOREING_VALUE;
48
import org.gvsig.fmap.dal.DataTypes;
49
import org.gvsig.fmap.dal.SQLBuilder;
50
import org.gvsig.fmap.dal.exception.DataException;
51
import org.gvsig.fmap.dal.exception.InitializeException;
52
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
53
import org.gvsig.fmap.dal.feature.FeatureQueryOrder;
54
import org.gvsig.fmap.dal.feature.FeatureType;
55
import org.gvsig.fmap.dal.feature.ForeingKey;
56

    
57
import org.gvsig.fmap.dal.feature.spi.FeatureProvider;
58
import org.gvsig.fmap.dal.feature.spi.SQLBuilderBase;
59
import org.gvsig.fmap.dal.resource.exception.AccessResourceException;
60
import org.gvsig.fmap.dal.resource.spi.ResourceConsumer;
61
import org.gvsig.fmap.dal.resource.spi.ResourceProvider;
62
import org.gvsig.fmap.dal.spi.DataServerExplorerProviderServices;
63
import org.gvsig.fmap.dal.spi.DataStoreProviderServices;
64
import org.gvsig.fmap.dal.store.db.DBHelper;
65
import org.gvsig.fmap.dal.store.jdbc.JDBCConnectionParameters;
66
import org.gvsig.fmap.dal.store.jdbc.JDBCLibrary;
67
import org.gvsig.fmap.dal.store.jdbc.JDBCNewStoreParameters;
68
import org.gvsig.fmap.dal.store.jdbc.JDBCResource;
69
import org.gvsig.fmap.dal.store.jdbc.JDBCServerExplorerParameters;
70
import org.gvsig.fmap.dal.store.jdbc.JDBCStoreParameters;
71
import org.gvsig.fmap.dal.store.jdbc.exception.JDBCCantFetchValueException;
72
import org.gvsig.fmap.dal.store.jdbc2.JDBCHelper;
73
import org.gvsig.fmap.dal.store.jdbc2.JDBCServerExplorer;
74
import org.gvsig.fmap.dal.store.jdbc2.JDBCStoreProvider;
75
import org.gvsig.fmap.dal.store.jdbc2.JDBCUtils;
76
import org.gvsig.fmap.dal.store.jdbc2.OperationsFactory;
77
import org.gvsig.fmap.dal.store.jdbc2.ResulSetControler;
78
import org.gvsig.fmap.dal.store.jdbc2.ResulSetControler.ResultSetEntry;
79
import org.gvsig.fmap.geom.Geometry;
80
import org.gvsig.fmap.geom.GeometryLocator;
81
import org.gvsig.fmap.geom.GeometryManager;
82
import org.gvsig.tools.dispose.impl.AbstractDisposable;
83
import org.gvsig.tools.evaluator.Evaluator;
84
import org.gvsig.tools.exception.BaseException;
85
import org.gvsig.tools.exception.NotYetImplemented;
86
import org.gvsig.tools.visitor.VisitCanceledException;
87
import org.gvsig.tools.visitor.Visitor;
88
import org.slf4j.Logger;
89
import org.slf4j.LoggerFactory;
90

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

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

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

    
100
  private ResulSetControler resulSetControler = null;
101

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

    
106
  private GeometryManager geometryManager = null;
107

    
108
  private JDBCConnectionParameters connectionParameters;
109

    
110
  private JDBCStoreProvider store;
111

    
112
  protected OperationsFactory operationsFactory = null;
113

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

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

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

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

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

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

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

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

    
168

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
306
    return isCompatible.booleanValue();
307
  }
308

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

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

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

    
360
  }
361

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
807
        // A?adimos a la SQL los "LEFT JOIN" que toca para poder acceder
808
        // a los valores referenciados por la funcion FOREING_VALUE.
809
        // Ademas a?adimos los valores referenciados por la funcion FOREING_VALUE
810
        // como columnas de la SQL para poder acceder a ellos si fuese necesario.
811
        HashSet usedLeftJoins = new HashSet();
812
      for (String foreingName : foreing_value_args) {
813
        String[] foreingNameParts = foreingName.split("[.]");
814
        if (foreingNameParts.length != 2) {
815
          continue;
816
        }
817
        String columnNameLocal = foreingNameParts[0];
818
        String columnNameForeing = foreingNameParts[1];
819
        FeatureAttributeDescriptor attr = type.getAttributeDescriptor(columnNameLocal);
820
        if (attr.isForeingKey()) {
821
          ForeingKey foreingKey = attr.getForeingKey();
822
          SQLBuilder.TableNameBuilder foreingTable = sqlbuilder.createTableNameBuilder()
823
                  .database(table.getDatabase())
824
                  .schema(table.getSchema())
825
                  .name(foreingKey.getTableName());
826
          SQLBuilder.TableNameBuilder mainTable = sqlbuilder.createTableNameBuilder()
827
                  .database(table.getDatabase())
828
                  .schema(table.getSchema())
829
                  .name(table.getName());
830
          
831
          if (!usedLeftJoins.contains(foreingTable.getName())) {
832
            sqlbuilder.select().from()
833
                    .left_join(
834
                            foreingTable,
835
                            expbuilder.eq(
836
                                    sqlbuilder.column(mainTable, columnNameLocal),
837
                                    sqlbuilder.column(foreingTable, foreingKey.getCodeName())
838
                            )
839
                    );
840
            usedLeftJoins.add(foreingTable.getName());
841
          }
842
          if (!sqlbuilder.select().has_group_by()) {
843
                sqlbuilder.select().column().name(foreingTable, columnNameForeing);
844
          }
845
        }
846
      }
847

    
848
    } catch (Throwable th) {
849
      LOGGER.warn("Can't replace FORENG_VALUE function.", th);
850
      throw th;
851
    } finally {
852
      LOGGER.trace("Exit from replaceForeingValueFunction.");
853
    }
854
  }
855
}