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

History | View | Annotate | Download (21.6 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 = "";
44
        this.supportSchemas = false;
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})";
64
        this.type_numeric_ps = "DECIMAL({0},{1})";
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})";
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_schema() {
92
            return false;
93
        }
94

    
95
        @Override
96
        public boolean has_database() {
97
            return false;
98
        }
99
        
100
    }
101

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

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

    
262
    }
263
    
264
    protected class H2SpatialCreateTableBuilder extends CreateTableBuilderBase {
265

    
266
        @Override
267
        public List<String> toStrings(Formatter formatter) {
268

    
269
            List<String> sqls = new ArrayList<>();
270
            StringBuilder builder = new StringBuilder();
271

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

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

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

    
397
            if ( this.has_from() ) {
398
                builder.append(" FROM ");
399
                builder.append(this.from.toString(formatter));
400
            }
401
            if ( this.has_where() ) {
402
                builder.append(" WHERE ");
403
                builder.append(this.where.toString(formatter));
404
            }
405
            
406
            if( this.has_order_by() ) {
407
                builder.append(" ORDER BY ");
408
                first = true;
409
                for (OrderByBuilder item : this.order_by) {
410
                    if (first) {
411
                        first = false;
412
                    } else {
413
                        builder.append(", ");
414
                    }
415
                    builder.append(item.toString(formatter));                    
416
                }   
417
            }
418
            
419
            if ( this.has_limit() && this.has_offset() ) {
420
                builder.append(" LIMIT ");
421
                builder.append(this.limit);
422
                builder.append(" OFFSET ");
423
                builder.append(this.offset);
424
                
425
            } else if ( this.has_limit()) {
426
                builder.append(" LIMIT ");
427
                builder.append(this.limit);
428

    
429
            } else if ( this.has_offset() ) {
430
                builder.append(" LIMIT -1 OFFSET ");
431
                builder.append(this.offset);    
432
            }
433
            return builder.toString();
434

    
435
        }
436
    }
437

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

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

    
552
    @Override
553
    protected AlterTableBuilder createAlterTableBuilder() {
554
        return new H2SpatialAlterTableBuilderBase();
555
    }
556
            
557
    @Override
558
    protected TableNameBuilder createTableNameBuilder() {
559
        return new H2SpatialTableNameBuilderBase();
560
    }
561
    
562
    @Override
563
    protected CreateTableBuilder createCreateTableBuilder() {
564
        return new H2SpatialCreateTableBuilder();
565
    }
566

    
567
    @Override
568
    protected SelectBuilder createSelectBuilder() {
569
        return new H2SpatialSelectBuilderBase();
570
    }
571

    
572
    @Override
573
    protected CreateIndexBuilder createCreateIndexBuilder() {
574
        return new H2SpatialCreateIndexBuilder();
575
    }
576

    
577
}