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 / operations / ResultSetForSetProviderOperation.java @ 46507

History | View | Annotate | Download (35.8 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.operations;
25

    
26
import java.util.ArrayList;
27
import java.util.HashMap;
28
import java.util.List;
29
import java.util.Map;
30
import org.apache.commons.lang3.ArrayUtils;
31
import org.apache.commons.lang3.StringUtils;
32
import org.gvsig.expressionevaluator.Code;
33
import org.gvsig.expressionevaluator.Expression;
34
import org.gvsig.expressionevaluator.ExpressionBuilder;
35
import static org.gvsig.expressionevaluator.ExpressionBuilder.FUNCTION_LET;
36
import static org.gvsig.expressionevaluator.ExpressionBuilder.VALUE_NULL;
37
import org.gvsig.expressionevaluator.ExpressionUtils;
38
import org.gvsig.expressionevaluator.GeometryExpressionBuilder;
39
import org.gvsig.fmap.dal.SQLBuilder;
40
import org.gvsig.fmap.dal.SQLBuilder.Column;
41
import org.gvsig.fmap.dal.SQLBuilder.OrderByBuilder;
42
import org.gvsig.fmap.dal.SQLBuilder.SelectBuilder;
43
import org.gvsig.fmap.dal.exception.DataException;
44
import org.gvsig.fmap.dal.expressionevaluator.FeatureAttributeEmulatorExpression;
45
import org.gvsig.fmap.dal.feature.EditableFeatureAttributeDescriptor;
46
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
47
import org.gvsig.fmap.dal.feature.FeatureAttributeEmulator;
48
import org.gvsig.fmap.dal.feature.FeatureQuery;
49
import org.gvsig.fmap.dal.feature.FeatureQueryOrder;
50
import org.gvsig.fmap.dal.feature.FeatureQueryOrder.FeatureQueryOrderMember;
51
import org.gvsig.fmap.dal.feature.FeatureType;
52
import org.gvsig.fmap.dal.store.jdbc2.JDBCConnection;
53
import org.gvsig.fmap.dal.store.jdbc2.JDBCHelper;
54
import static org.gvsig.fmap.dal.store.jdbc2.JDBCServerExplorer.QUERY_DONT_ADD_PRIMARY_KEY_IN_THE_ORDER;
55
import org.gvsig.fmap.dal.store.jdbc2.OperationsFactory.TableReference;
56
import org.gvsig.fmap.dal.store.jdbc2.ResulSetControler.ResultSetEntry;
57
import org.gvsig.fmap.dal.store.jdbc2.spi.JDBCSQLBuilderBase;
58
import static org.gvsig.fmap.dal.store.jdbc2.spi.JDBCSQLBuilderBase.PROP_FEATURE_TYPE;
59
import static org.gvsig.fmap.dal.store.jdbc2.spi.JDBCSQLBuilderBase.PROP_JDBCHELPER;
60
import static org.gvsig.fmap.dal.store.jdbc2.spi.JDBCSQLBuilderBase.PROP_QUERY;
61
import static org.gvsig.fmap.dal.store.jdbc2.spi.JDBCSQLBuilderBase.PROP_SYMBOLTABLE;
62
import static org.gvsig.fmap.dal.store.jdbc2.spi.JDBCSQLBuilderBase.PROP_TABLE;
63
import org.gvsig.fmap.geom.DataTypes;
64
import org.gvsig.tools.dataTypes.DataTypeUtils;
65
import org.gvsig.tools.dynobject.DynField;
66
import org.gvsig.tools.evaluator.Evaluator;
67
import org.gvsig.tools.util.ChainedIterable;
68
import static org.gvsig.fmap.dal.store.jdbc2.spi.JDBCHelperBase.ADD_TABLE_NAME_TO_COLUMNS;
69

    
70
public class ResultSetForSetProviderOperation extends AbstractConnectionOperation {
71

    
72
    private final TableReference table;
73
    private final String baseFilter;
74
    private final String baseOrder;
75
    private final FeatureType storeType;
76
    private final FeatureType setType;
77
    private final FeatureQuery query;
78
    private final long limit;
79
    private final long offset;
80
    private final int fetchSize;
81

    
82
    public ResultSetForSetProviderOperation(
83
            JDBCHelper helper,
84
            TableReference table,
85
            String baseFilter,
86
            String baseOrder,
87
            FeatureQuery query,
88
            FeatureType storeType,
89
            FeatureType setType,
90
            long limit,
91
            long offset,
92
            int fetchSize
93
    ) {
94
        super(helper);
95
        this.table = table;
96
        this.baseFilter = baseFilter;
97
        this.baseOrder = baseOrder;
98
        this.storeType = storeType;
99
        this.setType = setType;
100
        this.query = query;
101
        this.limit = limit;
102
        this.offset = offset;
103
        this.fetchSize = fetchSize;
104
    }
105

    
106
    @Override
107
    protected Object perform_operation() throws Exception {
108
        ResultSetEntry rs = createResultSet();
109
        return rs;
110
    }
111

    
112
    @Override
113
    public Object perform(JDBCConnection conn) throws DataException {
114
        throw new UnsupportedOperationException("Not supported yet.");
115
    }
116

    
117
    public String getSQL() {
118
        List<FeatureAttributeDescriptor> columns = new ArrayList<>();
119
        JDBCSQLBuilderBase sqlbuilder = createSQLBuilder();
120
        String sql = this.getSQL(sqlbuilder, columns, null);
121
        return sql;
122
    }
123

    
124
    public String getSQL(
125
            JDBCSQLBuilderBase sqlbuilder,
126
            List<FeatureAttributeDescriptor> columns,
127
            List<String> extraColumnNames
128
    ) {
129
        double tolerance = -1; //query.getScale(); 
130
        ExpressionBuilder expbuilder = sqlbuilder.expression();
131
        SelectBuilder select = sqlbuilder.select();
132
        select.from().table()
133
                .database(this.table.getDatabase())
134
                .schema(this.table.getSchema())
135
                .name(this.table.getTable());
136
        select.from().subquery(this.table.getSubquery());
137

    
138

    
139
        List<ExpressionBuilder.Value> valuesToRemoveFeatureType = new ArrayList<>();
140
        
141
        if( shouldUseACustomSelect() ) {
142
            return processCustomSelect(select, expbuilder, sqlbuilder, valuesToRemoveFeatureType, columns);
143
        }
144
        
145

    
146
        Map<String, EditableFeatureAttributeDescriptor> allExtraColumns = getAllExtraColumns(this.setType, this.query);
147

    
148
        List<String> primaryKeys = new ArrayList<>();
149
        for (FeatureAttributeDescriptor attr : storeType.getPrimaryKey()) {
150
            primaryKeys.add(attr.getName());
151
        }
152
        
153
        //Don't change order of this processes
154
        process1_SimpleFields(helper, setType, query, sqlbuilder, select, columns, primaryKeys, extraColumnNames);
155
        process2_ComputedFields(helper, setType, query, sqlbuilder, select, extraColumnNames);
156
        process3_Where(helper, storeType, query, sqlbuilder, select);
157
        process4_Aggregates(this.table, this.setType, this.query, allExtraColumns, sqlbuilder, select, extraColumnNames);
158
        process5_GroupBys(this.table, this.setType, this.query, allExtraColumns, sqlbuilder, select, extraColumnNames);
159
        process6_OrderBy(storeType, setType, query, allExtraColumns, sqlbuilder, select, primaryKeys, extraColumnNames, valuesToRemoveFeatureType);
160
        
161
        if (!StringUtils.isEmpty(baseFilter)) {
162
            select.where().and(expbuilder.toValue(baseFilter));
163
        }
164

    
165
        if (!StringUtils.isEmpty(baseOrder)) {
166
            select.order_by().custom(baseOrder);
167
        }
168

    
169
        
170
        if (limit > 0) {
171
            select.limit(limit);
172
        } else {
173
            select.limit(query == null ? null : query.getLimit());
174
        }
175
        if (offset > 0) {
176
            select.offset(offset);
177
        }
178
        sqlbuilder.setProperties(
179
                null,
180
                PROP_FEATURE_TYPE, this.storeType,
181
                PROP_TABLE, table,
182
                PROP_SYMBOLTABLE, this.query==null? null:this.query.getSymbolTable(),
183
                PROP_JDBCHELPER, this.helper,
184
                PROP_QUERY, this.query
185
        );
186
        for (ExpressionBuilder.Value value : valuesToRemoveFeatureType) {
187
            value.setProperty(PROP_FEATURE_TYPE, null);
188
        }
189
        this.helper.expandCalculedColumns(sqlbuilder);
190
        this.helper.processSpecialFunctions(sqlbuilder, storeType, extraColumnNames);
191
        String sql = sqlbuilder.toString();
192
        LOGGER.debug(sql);
193
        return sql;
194
    }
195

    
196
    private String processCustomSelect(SelectBuilder select, ExpressionBuilder expbuilder, JDBCSQLBuilderBase sqlbuilder, List<ExpressionBuilder.Value> valuesToRemoveFeatureType, List<FeatureAttributeDescriptor> columns) {
197
        String sql = table.getSubquery();
198
        if( StringUtils.containsAny(sql, "${where_filter}", "${and_filter}") ) {
199
            Evaluator filter = query.getFilter();
200
            if (filter != null) {
201
                String sqlfilter = filter.getSQL();
202
                if (!StringUtils.isEmpty(sqlfilter)) {
203
                    if (this.helper.supportFilter(this.storeType, filter)) {
204
                        select.where().set(expbuilder.toValue(sqlfilter));
205
                    }
206
                }
207
            }
208
            if (!StringUtils.isEmpty(baseFilter)) {
209
                select.where().and(expbuilder.toValue(baseFilter));
210
            }
211
        }
212
        if( StringUtils.containsAny(sql, "${order_by_orderspec}", "${comma_orderspec}") ) {
213
            FeatureQueryOrder order = query.getOrder();
214
            if (order != null) {
215
                for (FeatureQueryOrderMember member : order.members()) {
216
                    String attrName = member.getAttributeName();
217
                    ExpressionBuilder.Variable col = expbuilder.column(attrName);
218
                    select.order_by().value(col).ascending(member.getAscending());
219
                }
220
            }
221
        }
222
        if( select.has_where() || select.has_order_by() ) {
223
            sqlbuilder.setProperties(
224
                    null,
225
                    PROP_FEATURE_TYPE, this.storeType,
226
                    PROP_TABLE, table,
227
                    PROP_SYMBOLTABLE, this.query==null? null:this.query.getSymbolTable(),
228
                    PROP_JDBCHELPER, this.helper,
229
                    PROP_QUERY, this.query
230
            );
231
            for (ExpressionBuilder.Value value : valuesToRemoveFeatureType) {
232
                value.setProperty(PROP_FEATURE_TYPE, null);
233
            }
234
            if( select.has_where()) {
235
                String s = select.where().toString();
236
                sql = StringUtils.replace(sql, "${where_filter}", "WHERE " + s);
237
                sql = StringUtils.replace(sql, "${and_filter}", "AND (" + s + ")");
238
            }
239
            if( select.has_order_by() ) {
240
                String s = select.order_by().toString();
241
                sql = StringUtils.replace(sql, "${order_by_orderspec}", "ORDER BY " + s);
242
                sql = StringUtils.replace(sql, "${comma_orderspec}", ", "+ s);
243
            }
244
        }
245
        for (FeatureAttributeDescriptor attr : storeType) {
246
            columns.add(attr);
247
        }
248
        return sql;
249
    }
250
    
251
    public static void process1_SimpleFields(
252
            JDBCHelper helper, 
253
            FeatureType setType, 
254
            FeatureQuery query, 
255
            SQLBuilder sqlbuilder, 
256
            SelectBuilder select, 
257
            List<FeatureAttributeDescriptor> columns,
258
            List<String> primaryKeys,
259
            List<String> extraColumnNames
260
        ){
261
        if(!select.from().table().has_name()){
262
            throw new IllegalStateException("Need table in FROM.");
263
        }
264
        
265
        GeometryExpressionBuilder expbuilder = sqlbuilder.expression();
266
        
267
        List<String> forcedColumns = new ArrayList<>(primaryKeys);
268

    
269
        String[] constantsAttributeNames = null;
270
        if (query != null && query.hasConstantsAttributeNames()) {
271
            constantsAttributeNames = query.getConstantsAttributeNames();
272
        }
273
        
274
        for (FeatureAttributeDescriptor attr : setType) {
275
            if (attr.isComputed()) {
276
                // Campos calculados se a?aden despues para asegurarnos que
277
                // primeros se a?aden los campos reales y al final los calculados
278
                continue;
279
            }
280
            if (ArrayUtils.contains(constantsAttributeNames, attr.getName())) {
281
                continue;
282
            }
283
            if (attr.isPrimaryKey()) {
284
                forcedColumns.remove(attr.getName());
285
            }
286
            if (attr.getType() == DataTypes.GEOMETRY) {
287
                select.column().name(attr.getName()).as_geometry();
288
            } else {
289
                select.column().name(attr.getName());
290
            }
291
            columns.add(attr);
292
        }
293
        
294
        if (query == null || !query.hasGroupByColumns()) {
295
            for (String attrName : forcedColumns) {
296
                select.column().name(attrName);
297
                FeatureAttributeDescriptor attr = setType.getAttributeDescriptor(attrName);
298
                // Ojo, que cuando estamos pintando una vista NO TENEMOS LA PK y attr vale null!!!!
299
                // No se si deberiamos forzar a que cuando se crea un subfeaturetype se meta siempre
300
                // la pk, o simplemente nunca asumir que la vamos a tener.
301
                if (attr != null) {
302
                    columns.add(attr);
303
                }
304
            }
305
        }
306
    }
307

    
308
    public static void process2_ComputedFields(JDBCHelper helper, FeatureType setType, FeatureQuery query, SQLBuilder sqlbuilder, SelectBuilder select, List<String> extraColumnNames){
309
        if(!select.from().table().has_name()){
310
            throw new IllegalStateException("Need table in FROM.");
311
        }
312
//        En las columnas del select, si hay funciones de agregado en el select o un GROUP BY
313
//        los campos calculados que no lleven funci?n de agregado no deben incluir el nombre de tabla
314

    
315
        for (FeatureAttributeDescriptor attr : attributeDescriptorsIterable(setType, query) ) {
316
            if (attr.isComputed()) {
317
                ExpressionBuilder.Value value = createValueFromAttribute(sqlbuilder, attr);
318
                if( value != VALUE_NULL ) {
319
                    select.column()
320
                            .name(attr.getName())
321
                            .value(value)
322
                            .as(attr.getName());
323
                    if (extraColumnNames != null && !extraColumnNames.contains(attr.getName())) {
324
                        extraColumnNames.add(attr.getName());
325
                    }
326
//                    if(query != null && (query.hasGroupByColumns() || query.hasAggregateFunctions())){
327
                        addTableNameToColumns(sqlbuilder, value, true);
328
//                    }
329
                }
330
            }
331
        }
332
                
333
    }
334

    
335
    public static void process4_Aggregates(TableReference table, FeatureType setType, FeatureQuery query, Map<String, EditableFeatureAttributeDescriptor> allExtraColumns, SQLBuilder sqlbuilder, SelectBuilder select, List<String> extraColumnNames) throws RuntimeException {
336
        if(query == null){
337
            return;
338
        }
339
        if(!select.from().table().has_name()){
340
            throw new IllegalStateException("Need table in FROM.");
341
        }
342
        GeometryExpressionBuilder expbuilder = sqlbuilder.expression();
343
        if (query.hasAggregateFunctions() ) {
344
            // Ponemos NULL AS... en las columnas que no tengan funcion de argregado y no esten en el group by.
345
            for (FeatureAttributeDescriptor attr : attributeDescriptorsIterable(setType,query) )  {
346
                final String name = attr.getName();
347
                if( select.has_column(name) ) {
348
//                    if( attr.isComputed() ) {
349
//                        if (!query.isAggregate(name) ) {
350
//                            select.column(name).value(VALUE_NULL).as(name);
351
//                        }
352
//                    } else {
353
                        if ( !query.isAGroupByColumn(name) && !query.isAggregate(name) ) {
354
                            select.column(name).value(VALUE_NULL).as(name);
355
                        }
356
//                    }
357
                }
358
            }
359
            
360
            for (Map.Entry<String, String> entry : query.getAggregateFunctions().entrySet()) {
361
                String aggregateFunction = entry.getValue();
362
                String aggregateExpression = entry.getKey();
363
                FeatureAttributeDescriptor attr = getAttributeDescriptor(aggregateExpression, setType, allExtraColumns);
364
                if (attr == null) {
365
                    //Aggregate_expression AS name -> name:=aggregate_expression
366
                    Expression exp = ExpressionUtils.createExpression(aggregateExpression);
367
                    Code code = exp.getCode();
368
                    if (!(code instanceof Code.Callable)) {
369
                        throw new RuntimeException("Not able to use aggregate function with this expression(1): " + aggregateExpression);
370
                    }
371
                    Code.Callable callable = (Code.Callable) code;
372
                    if (!callable.name().equalsIgnoreCase(FUNCTION_LET)) {
373
                        throw new RuntimeException("Not able to use aggregate function with this expression(2): " + aggregateExpression);
374
                    }
375
                    String name = ((Code.Identifier) callable.parameters().get(0)).name();
376
                    ExpressionBuilder.Value value = callable.parameters().get(1).toValue();
377
                    
378
                    ExpressionBuilder.Function aggregateExp = expbuilder.function(aggregateFunction, value);                    
379
                    select.column(name).value(aggregateExp).as(name);
380
                } else {
381
                    SQLBuilder.SelectColumnBuilder column = select.column(attr.getName());
382
                    ExpressionBuilder.Value value = createValueFromAttribute(sqlbuilder, attr);
383
                    ExpressionBuilder.Function aggregateExp = expbuilder.function(aggregateFunction, value);
384
                    addTableNameToColumns(sqlbuilder, aggregateExp, true);
385
                    column.value(aggregateExp).as(attr.getName());
386
                }
387
            }
388
        }
389
    }
390

    
391
    public static void process5_GroupBys(TableReference table, FeatureType setType, FeatureQuery query, Map<String, EditableFeatureAttributeDescriptor> allExtraColumns, SQLBuilder sqlbuilder, SelectBuilder select, List<String> extraColumnNames) throws RuntimeException {
392
        if(query == null){
393
            return;
394
        }
395
        if(!select.from().table().has_name()){
396
            throw new IllegalStateException("Need table in FROM.");
397
        }
398
        if (query.hasGroupByColumns()) { // if tiene agrupacion
399
            GeometryExpressionBuilder expbuilder = sqlbuilder.expression();
400
            for (FeatureAttributeDescriptor attr : attributeDescriptorsIterable(setType,query.getExtraColumns()) ) {
401
                final String name = attr.getName();
402
                if( select.has_column(name) ) {
403
//                    if( attr.isComputed() ) {
404
//                        if (!query.isAggregate(name) ) {
405
//                            select.column(name).value(VALUE_NULL).as(name);
406
//                        }
407
//                    } else {
408
                        if ( !query.isAGroupByColumn(name) && !query.isAggregate(name) ) {
409
                            select.column(name).value(VALUE_NULL).as(name);
410
                        }
411
//                    }
412
                }
413
            }
414

    
415
            for (String attrName : query.getGroupByColumns()) {
416
                FeatureAttributeDescriptor attr = getAttributeDescriptor(attrName,setType, allExtraColumns);
417
                if ( attr == null ) {
418
                    try {
419
                        Code code = ExpressionUtils.compile(attrName);
420
                        select.group_by(code.toValue());
421
                    } catch (Exception ex) {
422
                        throw new RuntimeException("Not able to create column by expression in groupby query", ex);
423
                    }
424
                } else {
425
                    ExpressionBuilder.Value value = createValueFromAttribute(sqlbuilder, attr);
426
                    if( value instanceof Column ) {
427
                        ((Column)value).table(select.from().table());
428
                        if( select.getColumns().isEmpty() ) {
429
                            // Tratamos de asegurarnos de que al menos hay una columna en el select
430
                            select.column(attr.getName());
431
                        }
432
                    }
433
                    addTableNameToColumns(sqlbuilder, value, true);
434
                    select.group_by(value);
435
                }
436
            }
437
        }
438
    }
439
    
440
    public static void process3_Where(
441
            JDBCHelper helper, 
442
            FeatureType storeType, 
443
            FeatureQuery query, 
444
            SQLBuilder sqlbuilder,
445
            SelectBuilder select
446
        ) {
447
        if(query == null){
448
            return;
449
        }
450
        if(!select.from().table().has_name()){
451
            throw new IllegalStateException("Need table in FROM.");
452
        }
453
        GeometryExpressionBuilder expbuilder = sqlbuilder.expression();
454
        
455
        Evaluator filter = query.getFilter();
456
        if (filter != null) {
457
            String sqlfilter = filter.getSQL();
458
            if (!StringUtils.isEmpty(sqlfilter)) {
459
                if (helper.supportFilter(storeType, filter)) {
460
                    ExpressionBuilder.Value value = expbuilder.toValue(sqlfilter);
461
                    addTableNameToColumns(sqlbuilder, value, true);
462
                    select.where().set(value);
463
                }
464
            }
465
        }
466
    }
467
    
468
    public static void process6_OrderBy(
469
            FeatureType storeType, 
470
            FeatureType setType, 
471
            FeatureQuery query, 
472
            Map<String, EditableFeatureAttributeDescriptor> allExtraColumns, 
473
            SQLBuilder sqlbuilder,
474
            SelectBuilder select, 
475
            List<String> primaryKeys,
476
            List<String> extraColumnNames,
477
            List<ExpressionBuilder.Value> valuesToRemoveFeatureType
478
        ) {
479
        //
480
        // https://learnsql.com/cookbook/how-to-order-rows-by-group-sum-in-sql/
481
        //
482
        
483
        if(!select.from().table().has_name()){
484
            throw new IllegalStateException("Need table in FROM.");
485
        }
486
        GeometryExpressionBuilder expbuilder = sqlbuilder.expression();
487
        
488
        if (query != null) {
489
            FeatureQueryOrder order = query.getOrder();
490
            if (order != null) {
491
                if (query.hasAggregateFunctions() || query.hasGroupByColumns()) {
492
                    for (FeatureQueryOrderMember member : order.members()) {
493
                        String attrName = member.getAttributeName();
494
                        FeatureAttributeDescriptor attr = getAttributeDescriptor(attrName, setType, allExtraColumns);                        
495
                        if( attr == null ) {
496
                            // Order-by una expression en el query
497
                            String sqlorder = member.getEvaluator().getSQL();
498
                            select.order_by()
499
                                    .value(expbuilder.toValue(sqlorder))
500
                                    .ascending(member.getAscending());
501
                        } else {
502
                            ExpressionBuilder.Value value;
503
                            if ( query.isAGroupByColumn(attrName) ) {
504
                                value = createValueFromAttribute(sqlbuilder, attr);
505
                                if( value instanceof Column ) {
506
                                    ((Column)value).table(select.from().table());
507
                                }
508
                            } else if ( query.isAggregate(attrName) ) {
509
                                String fn = query.getAggregateFunctions().get(attr.getName());
510
                                value = createValueFromAttribute(sqlbuilder, attr);
511
                                if( value instanceof Column ) {
512
                                    ((Column)value).table(select.from().table());
513
                                }
514
                                value = expbuilder.function(fn, value);
515
                            } else {
516
                                throw new RuntimeException("Can't order by column '" + attrName + "', it does not have an aggregate function nor is it grouping by it.");
517
                            }
518
                            int nullsMode = OrderByBuilder.MODE_NULLS_LAST;
519
                            if ( !attr.allowNull() || attr.isPrimaryKey() ) {
520
                                // Puede ser costoso ordenar los nulos al principio o final, asi que
521
                                // si el campo no admite nulos pasamos de indicarselo.
522
                                nullsMode = OrderByBuilder.MODE_NULLS_NOT_SPECIFIED;
523
                            }
524

    
525
                            boolean expand;
526
                            if ( !query.isAggregate(attrName) ) {
527
                                if (select.has_aggregate_functions()) {
528
                                    expand = true;
529
                                } else {
530
                                    expand = false;
531
                                }
532
                            } else {
533
                                expand = false;
534
                            }
535
                            if (select.isGroupBy(attrName)) {
536
                                expand = false;
537
                            }
538

    
539
                            expandColumns(value, expand, valuesToRemoveFeatureType);
540
                            addTableNameToColumns(sqlbuilder, value, !attr.isComputed());                                
541
                            select.order_by().value(value).ascending(member.getAscending()).nulls(nullsMode);
542
                        }
543
                    }
544

    
545
                } else {
546
                    for (FeatureQueryOrderMember member : order.members()) {
547
                        String attrName = member.getAttributeName();
548
                        FeatureAttributeDescriptor attr = getAttributeDescriptor(attrName, setType, allExtraColumns);                        
549
                        if( attr == null ) {
550
                            // Order-by una expression en el query
551
                            String sqlorder = member.getEvaluator().getSQL();
552
                            select.order_by()
553
                                    .value(expbuilder.toValue(sqlorder))
554
                                    .ascending(member.getAscending());
555
                        } else {
556
                            Column column = sqlbuilder.column(attr.getName());
557
                            if( !attr.isComputed() ) {
558
                                column.table(select.from().table());
559
                            }
560
                            int nullsMode = OrderByBuilder.MODE_NULLS_LAST;
561
                            if ( !attr.allowNull() || attr.isPrimaryKey() ) {
562
                                // Puede ser costoso ordenar los nulos al principio o final, asi que
563
                                // si el campo no admite nulos pasamos de indicarselo.
564
                                nullsMode = OrderByBuilder.MODE_NULLS_NOT_SPECIFIED;
565
                            }
566
                            expandColumns(column, false, valuesToRemoveFeatureType);
567
                            addTableNameToColumns(sqlbuilder, column, !attr.isComputed());                                
568
                            select.order_by().value(column).ascending(member.getAscending()).nulls(nullsMode);
569
                        }
570
                    }
571
                }
572
            }
573
        }
574
        if( query!=null && !query.hasOrder() ) {
575
            // Si no hay orden y hay grupos ordenaremos por estos
576
            if( query.hasGroupByColumns() ) { // && isPaginated()) {
577
                // Cuando paginamos debemos ordenar por las columnas del groupby.
578
                // Ordenamos siempre para obtener el mismo resultado cuando paginamos
579
                // y no paginamos.
580
                for (String attrName : query.getGroupByColumns()) {
581
                    FeatureAttributeDescriptor attr = getAttributeDescriptor(attrName,setType, allExtraColumns);
582
                    if ( attr != null ) {
583
                        if( query.getOrder().get(attrName)==null ) {
584
                            ExpressionBuilder.Value value;
585
                            value = createValueFromAttribute(sqlbuilder, attr);
586
                            if( value instanceof Column ) {
587
                                ((Column)value).table(select.from().table());
588
                            }
589
                            int nullsMode = OrderByBuilder.MODE_NULLS_LAST;
590
                            if ( !attr.allowNull() || attr.isPrimaryKey() ) {
591
                                // Puede ser costoso ordenar los nulos al principio o final, asi que
592
                                // si el campo no admite nulos pasamos de indicarselo.
593
                                nullsMode = OrderByBuilder.MODE_NULLS_NOT_SPECIFIED;
594
                            }
595
                            expandColumns(value, true, valuesToRemoveFeatureType);
596
                            addTableNameToColumns(sqlbuilder, value, true);                                
597
                            select.order_by().value(value).ascending(true).nulls(nullsMode);
598
                        }
599
                    }
600
                }
601
            }
602
        }
603

    
604
        if (primaryKeys.isEmpty()) {
605
            // Muy probablemente si no tiene pk sea una vista, asi que 
606
            // pasaremos de ordenar y esperemos que la vista este ya ordenada.
607
            select.disable_check_order_and_offset();
608
        } else {
609
            if (query == null || !(query.hasAggregateFunctions() || query.hasGroupByColumns())) {
610
                // Siempre ordenamos por la clave primaria
611
                Object x = query == null ? null : query.getQueryParameter(QUERY_DONT_ADD_PRIMARY_KEY_IN_THE_ORDER);
612
                if (x == null || !DataTypeUtils.toBoolean(x)) {
613
                    for (String attrName : primaryKeys) {
614
                        if (select.getOrderBy(attrName) == null) {
615
                            ExpressionBuilder.Variable col = sqlbuilder.column(select.from().table(),attrName);
616
                            select.order_by()
617
                                    .value(col) //column(attrName)
618
                                    .ascending()
619
                                    .nulls(OrderByBuilder.MODE_NULLS_NOT_SPECIFIED)
620
                                    ;
621
                            // No tengo claro que pasa si se esta agrupando, y no se ha
622
                            // incluido en el agrupamiento al PK. En ese caso el select
623
                            // que se genera tendria una pinta tal que asi:
624
                            //   SELECT NULL as pk, ... ORDER BY pk ASC
625
                            // Probablemente en ese caso no tendriamos que meter la PK
626
                            // en el order-by ya que creo que no cambiaria el resultado.
627
                        }
628
                    }
629
                }
630
            }
631
        }
632

    
633
    }
634

    
635
    private boolean isPaginated() {
636
        // No tengo claro que (offset==0 && limit>0) sea lo mas correcto,
637
        // Pero cuando se va a paginar y se pide la primera pagina offset es
638
        // 0 y limit>0, y si no ordenamos ya esa primera pagina los resultados
639
        // que se obtienen no son correctos, ya que la primera pagina se saca
640
        // sin ordenar y el resto ordenadas.
641
        // Probablemente deberiamos tener alguna otra forma de detectar que
642
        // estamos paginanado ya que asi no distinguimo si solo queremos 
643
        // obtener los primeros elementos sin importarnos su orden.
644
        return (offset > 0 || (offset == 0 && limit > 0));
645
    }
646

    
647
    public ResultSetEntry createResultSet() throws DataException {
648
        List<FeatureAttributeDescriptor> columns = new ArrayList<>();
649
        List<String> extraColumnNames = new ArrayList<>();
650

    
651
        JDBCSQLBuilderBase sqlbuilder = createSQLBuilder();
652
        String sql = this.getSQL(sqlbuilder, columns, extraColumnNames);
653

    
654
        ResultSetEntry resultSetEntry = this.helper.getResulSetControler().create(
655
                sql, fetchSize,
656
                columns.toArray(new FeatureAttributeDescriptor[columns.size()]),
657
                extraColumnNames.toArray(new String[extraColumnNames.size()])
658
        );
659
        return resultSetEntry;
660
    }
661

    
662
    private boolean shouldUseACustomSelect() {
663
        if( !table.hasSubquery() ) {
664
            return false;
665
        }
666
        if( this.query == null ) {
667
            return false;
668
        }
669
        if( this.query.isUseSubquery() ) {
670
            return false;
671
        }
672
        if( this.query.hasGroupByColumns() ) {
673
            return false;
674
        }
675
        if( this.query.hasAggregateFunctions() ) {
676
            return false;
677
        }        
678
        if( !this.query.hasFilter() && !this.query.hasOrder() ) {
679
            return true;
680
        }
681
//        si el filtro es incompatible con la BBDD
682
//            return false
683
//        si el orden es incompatible con la BBDD
684
//            return false
685
//        si filtro u orden usan la funcion foreing_value 
686
//            return false;
687
        return true;
688
    }
689
    
690
    public static Map<String, EditableFeatureAttributeDescriptor> getAllExtraColumns(FeatureType setType, FeatureQuery query) {
691
        Map<String, EditableFeatureAttributeDescriptor> allExtraColumns = new HashMap<>();
692
        for (EditableFeatureAttributeDescriptor column : setType.getExtraColumns().getColumns()) {
693
            allExtraColumns.put(column.getName(), column);
694
        }
695
        if(query != null){
696
            for (EditableFeatureAttributeDescriptor column : query.getExtraColumn().getColumns()) {
697
                allExtraColumns.put(column.getName(), column);
698
            }
699
        }
700
        return allExtraColumns;
701
    }
702

    
703
    private static Iterable<FeatureAttributeDescriptor> attributeDescriptorsIterable(Iterable... iterables) {
704
        return new ChainedIterable<>(iterables);
705
    }
706

    
707
    private static Iterable<FeatureAttributeDescriptor> attributeDescriptorsIterable(FeatureType setType, FeatureQuery query) {
708
        if( query == null ) {
709
            return new ChainedIterable<>(setType);
710
        }
711
        return attributeDescriptorsIterable(setType, query.getExtraColumns());
712
    }
713

    
714
    private static boolean existsAttributeDescriptor(String attrName, FeatureType setType, Map<String, EditableFeatureAttributeDescriptor> allExtraColumns)  {
715
        return getAttributeDescriptor(attrName, setType, allExtraColumns)!=null;
716
    }
717

    
718
    private static FeatureAttributeDescriptor getAttributeDescriptor(String attrName, FeatureType setType, Map<String, EditableFeatureAttributeDescriptor> extraColumns) {
719
        FeatureAttributeDescriptor attr = null;
720
        if( setType!=null ) {
721
            attr = setType.getAttributeDescriptorFromAll(attrName);
722
        }
723
        if(attr == null && extraColumns!=null){
724
            attr = extraColumns.get(attrName);
725
        }
726
        return attr;
727
    }
728

    
729
    private static void expandColumns(ExpressionBuilder.Value value, boolean expand, List<ExpressionBuilder.Value> valuesToRemoveFeatureType) {
730
        if(!expand){
731
            // En el OrderBy no queremos que se sustituya el nombre del campo calculado
732
            // por su expresion. Se encarga el formater y lo evitamos quitandole el ftype
733
            // al value.
734
            valuesToRemoveFeatureType.add(value);
735
        }
736
    }
737
    private static void addTableNameToColumns(SQLBuilder sqlbuilder, ExpressionBuilder.Value expression, boolean add) {
738
        sqlbuilder.setProperties(expression, null, ADD_TABLE_NAME_TO_COLUMNS, add);
739
    }
740

    
741

    
742
    private static ExpressionBuilder.Value createValueFromAttribute(SQLBuilder sqlbuilder, FeatureAttributeDescriptor attr) {
743
        if (attr.getRelationType() != DynField.RELATION_TYPE_NONE) {
744
            return VALUE_NULL;
745
        }
746
        if( attr.isComputed() ) {
747
            FeatureAttributeEmulator attrEmulator = attr.getFeatureAttributeEmulator();
748
            if( !(attrEmulator instanceof FeatureAttributeEmulatorExpression) ) {
749
                return VALUE_NULL;
750
            }
751
            FeatureAttributeEmulatorExpression x = (FeatureAttributeEmulatorExpression) attrEmulator;
752
            Expression exp = x.getExpression();
753
            if (exp == null || exp.isEmpty() ) {
754
                return VALUE_NULL;
755
            }
756
            Code code = exp.getCode();
757
            ExpressionBuilder.Value value = code.toValue(sqlbuilder.expression());
758
            return value;
759
        }
760
        // Es un campo de la tabla
761
        Column column = sqlbuilder.column(attr.getName());
762
        return column;
763
    }
764
    
765
}