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.h2 / src / main / java / org / gvsig / fmap / dal / store / h2 / H2SpatialSQLBuilder.java @ 44361

History | View | Annotate | Download (22.2 KB)

1
package org.gvsig.fmap.dal.store.h2;
2

    
3
import java.sql.PreparedStatement;
4
import java.sql.SQLException;
5
import java.text.MessageFormat;
6
import java.util.ArrayList;
7
import java.util.Date;
8
import java.util.List;
9
import java.util.Objects;
10
import org.apache.commons.lang3.tuple.Pair;
11
import org.gvsig.expressionevaluator.ExpressionBuilder.Parameter;
12
import org.gvsig.expressionevaluator.Formatter;
13
import org.gvsig.fmap.dal.DataTypes;
14
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
15
import org.gvsig.fmap.dal.feature.FeatureType;
16
import org.gvsig.fmap.dal.feature.spi.FeatureProvider;
17
import org.gvsig.fmap.dal.store.h2.expressionbuilderformatter.H2SpatialFormatter;
18
import org.gvsig.fmap.dal.store.jdbc2.spi.JDBCSQLBuilderBase;
19
import org.gvsig.fmap.geom.Geometry;
20
import org.gvsig.fmap.geom.exception.CreateGeometryException;
21
import org.gvsig.tools.dispose.Disposable;
22

    
23
public class H2SpatialSQLBuilder extends JDBCSQLBuilderBase {
24

    
25
    protected Formatter formatter = null;
26
    
27
    public H2SpatialSQLBuilder(H2SpatialHelper helper) {
28
        super(helper);
29
        
30
        //
31
        // H2/H2GIS SQL functions reference list
32
        //
33
        // http://www.h2database.com/html/functions.html
34
        // http://www.h2gis.org/docs/1.3/functions/
35
        //
36
        // http://www.h2database.com/html/grammar.html
37
        //
38
        // http://www.h2database.com/html/datatypes.html
39
        //
40
        //
41

    
42
        
43
        this.defaultSchema = "PUBLIC";
44
        this.supportSchemas = true;
45
        this.allowAutomaticValues = true;
46
        this.geometrySupportType = this.helper.getGeometrySupportType();
47
        this.hasSpatialFunctions = this.helper.hasSpatialFunctions();
48

    
49
        this.STMT_DELETE_GEOMETRY_COLUMN_FROM_TABLE_schema_table = null;
50
        this.STMT_DELETE_GEOMETRY_COLUMN_FROM_TABLE_table = null;
51
         
52
        this.STMT_UPDATE_TABLE_STATISTICS_table = "ANALYZE SAMPLE_SIZE 0";
53

    
54
//        config.set(SQLConfig.ST_AsBinary, "NVL2({0}, ST_AsBinary({0}), null)");
55

    
56
        this.type_boolean = "BOOLEAN";
57
        this.type_byte = "TINYINT";
58
        this.type_bytearray = "BLOB";
59
        this.type_geometry = "GEOMETRY";
60
        this.type_char = "CHAR";
61
        this.type_date = "DATE";
62
        this.type_double = "DOUBLE"; 
63
        this.type_numeric_p = "DECIMAL({0,Number,#######})";
64
        this.type_numeric_ps = "DECIMAL({0,Number,#######},{1,Number,#######})";
65
        this.type_bigdecimal = "DOUBLE";
66
        this.type_float = "REAL";
67
        this.type_int = "INTEGER";
68
        this.type_long = "BIGINT";
69
        this.type_string = "VARCHAR";
70
        this.type_string_p = "VARCHAR({0,Number,#######})";
71
        this.type_time = "TIME";
72
        this.type_timestamp = "TIMESTAMP";
73
        this.type_version = "VARCHAR";
74
        this.type_URI = "VARCHAR";
75
        this.type_URL = "VARCHAR";
76
        this.type_FILE = "VARCHAR";
77
        this.type_FOLDER = "VARCHAR";        
78
    }
79

    
80
    @Override
81
    protected Formatter formatter() {
82
        if( this.formatter==null ) {
83
            this.formatter = new H2SpatialFormatter(this);
84
        }
85
        return this.formatter;
86
    }
87

    
88
    public class H2SpatialTableNameBuilderBase extends TableNameBuilderBase {
89

    
90
        @Override
91
        public boolean has_database() {
92
            return false;
93
        }
94
        
95
    }
96

    
97
    public class H2SpatialCreateIndexBuilder extends CreateIndexBuilderBase {
98
    
99
        @Override
100
        public List<String> toStrings(Formatter formatter) {
101
            StringBuilder builder = new StringBuilder();
102
            builder.append("CREATE ");
103
            if( this.isUnique ) {
104
                builder.append("UNIQUE ");
105
            }
106
            if( this.isSpatial ) {
107
                builder.append("SPATIAL ");
108
            }
109
            builder.append("INDEX ");
110
            if( this.ifNotExist ) {
111
                builder.append("IF NOT EXISTS ");
112
            }
113
            builder.append(as_identifier(this.indexName));
114
            builder.append(" ON ");
115
            builder.append(this.table.toString(formatter));
116
            builder.append(" ( ");
117
            boolean is_first_column = true;
118
            for( String column : this.columns) {
119
                if( is_first_column ) {
120
                    is_first_column = false;
121
                } else {
122
                    builder.append(", ");
123
                }
124
                builder.append(as_identifier(column));
125
            }
126
            builder.append(" )");
127
            
128
            List<String> sqls = new ArrayList<>();
129
            sqls.add(builder.toString());
130
            return sqls;
131
        }
132

    
133
    }
134
        
135
    protected class H2SpatialAlterTableBuilderBase extends AlterTableBuilderBase {
136
        @Override
137
        public List<String> toStrings(Formatter formatter) {
138
            List<String> sqls = new ArrayList<>();
139
            if( this.isEmpty() ) {
140
                return sqls;
141
            }
142
            for (String column : drops) {
143
                StringBuilder builder = new StringBuilder();
144
                builder.append("ALTER TABLE ");
145
                builder.append(this.table.toString(formatter));
146
                builder.append(" DROP COLUMN IF EXISTS ");
147
                builder.append(as_identifier(column)); 
148
                sqls.add(builder.toString());
149
            }
150
            for (ColumnDescriptor column : adds) {
151
                StringBuilder builder = new StringBuilder();
152
                builder.append("ALTER TABLE ");
153
                builder.append(this.table.toString(formatter));
154
                builder.append(" ADD COLUMN ");
155
                builder.append(as_identifier(column.getName())); 
156
                builder.append(" ");
157
                if( column.getType() == DataTypes.INT && column.isAutomatic() ) {
158
                    builder.append(" SERIAL");
159
                } else {
160
                    builder.append(
161
                        sqltype(
162
                            column.getType(), 
163
                            column.getPrecision(), 
164
                            column.getSize(),
165
                            column.getGeometryType(), 
166
                            column.getGeometrySubtype()
167
                        )
168
                    );
169
                }
170
                if (column.getDefaultValue() == null) {
171
                    if (column.allowNulls()) {
172
                        builder.append(" DEFAULT NULL");
173
                    }
174
                } else {
175
                    builder.append(" DEFAULT '");
176
                    builder.append(Objects.toString(column.getDefaultValue(),""));
177
                    builder.append("'");
178
                }
179
                if (column.allowNulls()) {
180
                    builder.append(" NULL");
181
                } else {
182
                    builder.append(" NOT NULL");
183
                }
184
                if (column.isPrimaryKey()) {
185
                    builder.append(" PRIMARY KEY");
186
                }
187
                sqls.add(builder.toString());
188
                
189
                if( column.isGeometry() ) {
190
                    String sql;
191
                    String constraint_name = "constraint_" + this.table().getName() + "_" + column.getName()+"_dim";
192
                    sql = MessageFormat.format(
193
                        "ALTER TABLE \"{0}\".\"{1}\" ADD CONSTRAINT IF NOT EXISTS \"{2}\" CHECK ST_CoordDim(\"{3}\") = {4}",
194
                        this.table().getSchema(),
195
                        this.table().getName(),
196
                        constraint_name,
197
                        column.getName(),
198
                        sqlgeometrynumdimension(column.getGeometryType(),column.getGeometrySubtype())
199
                    );
200
                    sqls.add(sql);
201
                }
202
            }
203
            for (ColumnDescriptor column : alters) {
204
                StringBuilder builder = new StringBuilder();
205
                builder.append("ALTER TABLE ");
206
                builder.append(this.table.toString(formatter));
207
                builder.append(" ALTER COLUMN ");
208
                builder.append(as_identifier(column.getName())); 
209
                builder.append(" ");
210
                builder.append(
211
                    sqltype(
212
                        column.getType(), 
213
                        column.getPrecision(), 
214
                        column.getSize(),
215
                        column.getGeometryType(), 
216
                        column.getGeometrySubtype()
217
                    )
218
                );
219
                if (column.getDefaultValue() == null) {
220
                    if (column.allowNulls()) {
221
                        builder.append(" DEFAULT NULL");
222
                    }
223
                } else {
224
                    builder.append(" DEFAULT '");
225
                    builder.append(column.getDefaultValue().toString());
226
                    builder.append("'");
227
                }
228
                if( column.isAutomatic() ) {
229
                    builder.append(" AUTO_INCREMENT");
230
                }
231
                sqls.add(builder.toString());
232
                if( column.isGeometry() ) {
233
                    String sql;
234
                    String constraint_name = "constraint_" + this.table().getName() + "_" + column.getName()+"_dim";
235
                    sql = MessageFormat.format(
236
                        "ALTER TABLE \"{0}\".\"{1}\" ADD CONSTRAINT IF NOT EXISTS \"{2}\" CHECK ST_CoordDim(\"{3}\") = {4}",
237
                        this.table().getSchema(),
238
                        this.table().getName(),
239
                        constraint_name,
240
                        column.getName(),
241
                        sqlgeometrynumdimension(column.getGeometryType(),column.getGeometrySubtype())
242
                    );
243
                    sqls.add(sql);
244
                }
245
            }
246
            for (Pair<String,String> pair : renames) {
247
                StringBuilder builder = new StringBuilder();
248
                builder.append("ALTER TABLE ");
249
                builder.append(this.table.toString(formatter));
250
                builder.append(" RENAME COLUMN ");
251
                builder.append(as_identifier(pair.getLeft())); 
252
                builder.append(" TO ");
253
                builder.append(as_identifier(pair.getRight())); 
254
                sqls.add(builder.toString());
255
            }
256
            return sqls;
257
        }
258

    
259
    }
260
    
261
    protected class H2SpatialCreateTableBuilder extends CreateTableBuilderBase {
262

    
263
        @Override
264
        public List<String> toStrings(Formatter formatter) {
265

    
266
            List<String> sqls = new ArrayList<>();
267
            StringBuilder builder = new StringBuilder();
268

    
269
            builder.append("CREATE TABLE ");
270
            builder.append(this.table.toString(formatter));
271
            builder.append(" (");
272
            boolean first = true;
273
            for (ColumnDescriptor column : columns) {
274

    
275
                if (first) {
276
                    first = false;
277
                } else {
278
                    builder.append(", ");
279
                }
280
                builder.append(as_identifier(column.getName()));
281
                builder.append(" ");
282
                builder.append(
283
                    sqltype(
284
                        column.getType(), 
285
                        column.getPrecision(), 
286
                        column.getSize(),
287
                        column.getGeometryType(), 
288
                        column.getGeometrySubtype()
289
                    )
290
                );
291
                if( !column.isGeometry() ) {
292
                    if (column.isPrimaryKey()) {
293
                        builder.append(" PRIMARY KEY");
294
                        if( column.isAutomatic() ) {
295
                            builder.append(" AUTO_INCREMENT");
296
                        }
297
                    } else {
298
                        if( column.isAutomatic() ) {
299
                            builder.append(" AUTO_INCREMENT");
300
                        }
301
                        if (column.getDefaultValue() == null) {
302
                            if (column.allowNulls()) {
303
                                builder.append(" DEFAULT NULL");
304
                            }
305
                        } else {
306
                            if( column.getType() == DataTypes.DATE ) {
307
                                builder.append(" DEFAULT ( TIMESTAMP '");
308
                                Date d = (Date) column.getDefaultValue();
309
                                builder.append(MessageFormat.format( "{0,date,yyyy-MM-dd HH:mm:ss.S}",d));
310
                                builder.append("' )");
311
                            } else {
312
                                builder.append(" DEFAULT '");
313
                                builder.append(Objects.toString(column.getDefaultValue(),""));
314
                                builder.append("'");
315
                            }
316
                        }
317
                    }
318
                }
319
                if (!column.allowNulls()) {
320
                    builder.append(" NOT NULL");
321
                }
322
            }
323
            builder.append(" )");
324
            sqls.add(builder.toString());
325
            for (ColumnDescriptor column : columns) {
326
                if( column.isGeometry() ) {
327
                    String sql;
328
                    String constraint_name = "constraint_" + this.table().getName() + "_" + column.getName()+"_dim";
329
                    sql = MessageFormat.format(
330
                        "ALTER TABLE \"{0}\".\"{1}\" ADD CONSTRAINT IF NOT EXISTS \"{2}\" CHECK NVL2(\"{3}\", ST_CoordDim(\"{3}\") = {4}, TRUE)",
331
                        this.table().getSchema(),
332
                        this.table().getName(),
333
                        constraint_name,
334
                        column.getName(),
335
                        sqlgeometrynumdimension(column.getGeometryType(),column.getGeometrySubtype())
336
                    );
337
                    if( column.getGeometrySRSId()!=null ) {
338
                        String sql2;
339
                        String constraint_name2 = "constraint_" + this.table().getName() + "_" + column.getName()+"_srid";
340
                        sql2 = MessageFormat.format(
341
                            "ALTER TABLE \"{0}\".\"{1}\" ADD CONSTRAINT IF NOT EXISTS \"{2}\" CHECK NVL2(\"{3}\", ST_SRID(\"{3}\") = {4,number,#####}, TRUE)",
342
                            this.table().getSchema(), // 0
343
                            this.table().getName(), // 1
344
                            constraint_name2, // 2
345
                            column.getName(), // 3
346
                            column.getGeometrySRSId() // 4
347
                        );
348
                        sqls.add(sql2);
349
                    }
350
                    sqls.add(sql);
351
                }
352
            }            
353
            
354
            return sqls;
355
        }
356
    }
357

    
358
    public class H2SpatialSelectBuilderBase extends SelectBuilderBase {
359
        
360
        @Override
361
        protected boolean isValid(StringBuilder message) {
362
            if( message == null ) {
363
                message = new StringBuilder();
364
            }
365
            if( this.has_offset() && !this.has_order_by() ) {
366
                // Algunos gestores de BBDD requieren que se especifique un
367
                // orden para poder usar OFFSET. Como eso parece buena idea para
368
                // asegurar que siempre tengamos los mismo resultados, lo exijimos
369
                // siempre.
370
                message.append("Can't use OFFSET without an ORDER BY.");
371
                return false;
372
            }
373
            return true;
374
        }        
375
        
376
        @Override
377
        public String toString(Formatter formatter) {
378
            StringBuilder builder = new StringBuilder();
379
            if( !isValid(builder) ) {
380
                throw new IllegalStateException(builder.toString());
381
            }
382
            builder.append("SELECT ");
383
            if( this.distinct ) {
384
                builder.append("DISTINCT ");
385
            }
386
            boolean first = true;
387
            for (SelectColumnBuilder column : columns) {
388
                if (first) {
389
                    first = false;
390
                } else {
391
                    builder.append(", ");
392
                }
393
                builder.append(column.toString(formatter));
394
            }
395

    
396
            if ( this.has_from() ) {
397
                builder.append(" FROM ");
398
                builder.append(this.from.toString(formatter));
399
            }
400
            if( this.has_group_by() ) {
401
                builder.append(" GROUP BY ");
402
                builder.append(this.groupColumn.get(0).name());
403
                for (int i = 1; i < groupColumn.size(); i++) {
404
                    builder.append(", ");
405
                    builder.append(this.groupColumn.get(i).name());
406
                }
407
            }
408
            if ( this.has_where() ) {
409
                builder.append(" WHERE ");
410
                builder.append(this.where.toString(formatter));
411
            }
412
            
413
            if( this.has_order_by() ) {
414
                builder.append(" ORDER BY ");
415
                first = true;
416
                for (OrderByBuilder item : this.order_by) {
417
                    if (first) {
418
                        first = false;
419
                    } else {
420
                        builder.append(", ");
421
                    }
422
                    builder.append(item.toString(formatter));                    
423
                }   
424
            }
425
            
426
            if ( this.has_limit() && this.has_offset() ) {
427
                builder.append(" LIMIT ");
428
                builder.append(this.limit);
429
                builder.append(" OFFSET ");
430
                builder.append(this.offset);
431
                
432
            } else if ( this.has_limit()) {
433
                builder.append(" LIMIT ");
434
                builder.append(this.limit);
435

    
436
            } else if ( this.has_offset() ) {
437
                builder.append(" LIMIT -1 OFFSET ");
438
                builder.append(this.offset);    
439
            }
440
            return builder.toString();
441

    
442
        }
443
    }
444

    
445
    @Override
446
    public Object sqlgeometrydimension(int type, int subtype) {
447
        //'XY' or 2: 2D points, identified by X and Y coordinates
448
        //'XYM': 2D points, identified by X and Y coordinates. Each point stores an M-value (measure) as well
449
        //'XYZ' or 3: 3D points, identified by X, Y and Z coordinates
450
        //'XYZM' or 4: 3D points, identified by X, Y and Z coordinates. Each point stores an M-value (measure) as well
451
        switch(subtype) {
452
            case Geometry.SUBTYPES.GEOM2D:
453
            default:
454
                return "XY";
455
            case Geometry.SUBTYPES.GEOM2DM:
456
                return "XYM";
457
            case Geometry.SUBTYPES.GEOM3D:
458
                return "XYZ";
459
            case Geometry.SUBTYPES.GEOM3DM:
460
                return "XYZM";
461
        }
462
    }
463

    
464
    @Override
465
    public String sqltype(int type, int p, int s, int geomtype, int geomSubtype) {
466
        if( type!=DataTypes.GEOMETRY ) {
467
            return super.sqltype(type, p, s, geomtype, geomSubtype);
468
        }
469
        //
470
        // https://github.com/orbisgis/h2gis/wiki/1.-Spatial-data#geometry-columns-view
471
        //
472
        switch(geomtype) {
473
        case Geometry.TYPES.POINT:
474
            return "POINT";
475
        case Geometry.TYPES.MULTIPOINT:
476
            return "MULTIPOINT";
477
        case Geometry.TYPES.LINE:
478
            return "LINESTRING";
479
        case Geometry.TYPES.MULTILINE:
480
            return "MULTILINESTRING";
481
        case Geometry.TYPES.POLYGON:
482
            return "POLYGON";
483
        case Geometry.TYPES.MULTIPOLYGON:
484
            return "MULTIPOLYGON";
485
        default:
486
            return "GEOMETRY";
487
        }
488
    }
489
    
490
    public Object sqlgeometrynumdimension(int type, int subtype) {
491
        int dimensions=2;
492
        switch(subtype) {
493
        case Geometry.SUBTYPES.GEOM3D:
494
            dimensions = 3;
495
            break;
496
        case Geometry.SUBTYPES.GEOM2D:
497
            dimensions = 2;
498
            break;
499
        case Geometry.SUBTYPES.GEOM2DM:
500
            dimensions = 2; // ??????
501
            break;
502
        case Geometry.SUBTYPES.GEOM3DM:
503
            dimensions = 3; // ??????
504
            break;
505
        }
506
        return dimensions;
507
    }
508
    
509
    @Override
510
    public H2SpatialHelper getHelper() {
511
        return (H2SpatialHelper) this.helper;
512
    }
513
   
514
    @Override
515
    public Disposable setParameters(PreparedStatement st, FeatureProvider feature) {
516
        try {
517
            FeatureType featureType = feature.getType();
518
            List<Object> values = new ArrayList<>();
519
            for (Parameter parameter : this.parameters()) {
520
                if (parameter.is_constant()) {
521
                    values.add(parameter.value());
522
                } else {
523
                    String name = parameter.name();
524
                    FeatureAttributeDescriptor descriptor = featureType.getAttributeDescriptor(name);
525
                    switch( descriptor.getType() ) {
526
                    case DataTypes.DATE:
527
                        Date value = (Date)(feature.get(name));
528
                        if( value == null ) {
529
                            values.add(null);
530
                        } else {
531
                            values.add(new java.sql.Date(value.getTime()));
532
                        }
533
                        break;
534
                    case DataTypes.GEOMETRY:
535
                        Geometry geom = this.forceGeometryType(
536
                            descriptor.getGeomType(),
537
                            (Geometry)(feature.get(name))
538
                        );
539
                        values.add(geom);
540
                        break;
541
                    default:
542
                        values.add(feature.get(name));
543
                        break;
544
                    }
545
                }
546
            }
547
            return this.setStatementParameters(st, values, this.geometry_support_type());
548
        } catch (SQLException | CreateGeometryException ex) {
549
            String f = "unknow";
550
            try {
551
                f = feature.toString();
552
            } catch (Exception ex2) {
553
                // Do nothing
554
            }
555
            throw new RuntimeException("Can't set parameters to prepared statement from the feature (" + f + ")", ex);
556
        }
557
    }
558

    
559
    @Override
560
    protected AlterTableBuilder createAlterTableBuilder() {
561
        return new H2SpatialAlterTableBuilderBase();
562
    }
563
            
564
    @Override
565
    public TableNameBuilder createTableNameBuilder() {
566
        return new H2SpatialTableNameBuilderBase();
567
    }
568
    
569
    @Override
570
    protected CreateTableBuilder createCreateTableBuilder() {
571
        return new H2SpatialCreateTableBuilder();
572
    }
573

    
574
    @Override
575
    protected SelectBuilder createSelectBuilder() {
576
        return new H2SpatialSelectBuilderBase();
577
    }
578

    
579
    @Override
580
    protected CreateIndexBuilder createCreateIndexBuilder() {
581
        return new H2SpatialCreateIndexBuilder();
582
    }
583

    
584
}