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

History | View | Annotate | Download (23.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 org.apache.commons.lang3.tuple.Pair;
10
import org.gvsig.fmap.dal.DataTypes;
11
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
12
import org.gvsig.fmap.dal.feature.FeatureType;
13
import org.gvsig.fmap.dal.feature.spi.FeatureProvider;
14
import org.gvsig.fmap.dal.store.jdbc2.spi.JDBCSQLBuilderBase;
15
import org.gvsig.fmap.geom.Geometry;
16
import org.gvsig.tools.dispose.Disposable;
17

    
18
public class H2SpatialSQLBuilder extends JDBCSQLBuilderBase {
19

    
20
    public H2SpatialSQLBuilder(H2SpatialHelper helper) {
21
        super(helper);
22
        
23
        //
24
        // H2/H2GIS SQL functions reference list
25
        //
26
        // http://www.h2database.com/html/functions.html
27
        // http://www.h2gis.org/docs/1.3/functions/
28
        //
29
        // http://www.h2database.com/html/grammar.html
30
        //
31
        // http://www.h2database.com/html/datatypes.html
32
        //
33
        //
34
        
35
        config.set(SQLConfig.default_schema, "");
36
        config.set(SQLConfig.support_schemas, false);
37
        config.set(SQLConfig.allowAutomaticValues, true);
38
        config.set(SQLConfig.geometry_type_support, this.helper.getGeometrySupportType());
39
        config.set(SQLConfig.has_spatial_functions, this.helper.hasSpatialFunctions());
40

    
41
        config.remove_functionality(SQLConfig.DELETE_GEOMETRY_COLUMN_FROM_TABLE_schema_table);
42
        config.remove_functionality(SQLConfig.DELETE_GEOMETRY_COLUMN_FROM_TABLE_table);
43
         
44
//        config.set(SQLConfig.UPDATE_TABLE_STATISTICS_table,"ANALYZE TABLE {0} SAMPLE_SIZE 0");
45
        config.set(SQLConfig.UPDATE_TABLE_STATISTICS_table,"ANALYZE SAMPLE_SIZE 0");
46

    
47
        config.set(SQLConfig.ST_GeomFromEWKB, "GeomFromWKB({0}, {1})");
48
        config.set(SQLConfig.ST_AsEWKB, "AsWKB(({0}))");        
49
        config.set(SQLConfig.ST_Intersects, "( (({0}) && ({1})) AND ST_Intersects(({0}),({1}) ))");  
50
        config.set(SQLConfig.ST_AsBinary, "NVL2({0}, ST_AsBinary({0}), null)");
51
        
52
        config.set(SQLConfig.lcase, "LOWER({0})");
53
        config.set(SQLConfig.ucase, "UPPER({0})");
54
        config.set(SQLConfig.operator_ILIKE, "({0}) ILIKE ({1})");
55
        config.set(SQLConfig.isNull, "( ({0}) IS NULL )");
56
        config.set(SQLConfig.notIsNull, "( ({0}) IS NOT NULL )");
57

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

    
82
    public class H2SpatialTableNameBuilderBase extends TableNameBuilderBase {
83

    
84
        @Override
85
        public boolean has_schema() {
86
            return false;
87
        }
88

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

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

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

    
256
    }
257
    
258
    protected class H2SpatialCreateTableBuilder extends CreateTableBuilderBase {
259

    
260
        @Override
261
        public List<String> toStrings() {
262

    
263
            List<String> sqls = new ArrayList<>();
264
            StringBuilder builder = new StringBuilder();
265

    
266
            builder.append("CREATE TABLE ");
267
            builder.append(this.table.toString());
268
            builder.append(" (");
269
            boolean first = true;
270
            for (ColumnDescriptorBuilder column : columns) {
271

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

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

    
387
            if ( this.has_from() ) {
388
                builder.append(" FROM ");
389
                builder.append(this.from.toString());
390
            }
391
            if ( this.has_where() ) {
392
                builder.append(" WHERE ");
393
                builder.append(this.where.toString());
394
            }
395
            
396
            if( this.has_order_by() ) {
397
                builder.append(" ORDER BY ");
398
                first = true;
399
                for (OrderByBuilder item : this.order_by) {
400
                    if (first) {
401
                        first = false;
402
                    } else {
403
                        builder.append(", ");
404
                    }
405
                    builder.append(item.toString());                    
406
                }   
407
            }
408
            
409
            if ( this.has_limit() && this.has_offset() ) {
410
                builder.append(" LIMIT ");
411
                builder.append(this.limit);
412
                builder.append(" OFFSET ");
413
                builder.append(this.offset);
414
                
415
            } else if ( this.has_limit()) {
416
                builder.append(" LIMIT ");
417
                builder.append(this.limit);
418

    
419
            } else if ( this.has_offset() ) {
420
                builder.append(" LIMIT -1 OFFSET ");
421
                builder.append(this.offset);    
422
            }
423
            return builder.toString();
424

    
425
        }
426
    }
427

    
428
    @Override
429
    public String bytearray(byte[] data) {
430
        // H2Spatial usa un formato diferencte para especificar un array de 
431
        // bytes. En lugar de 0x... usa x'...' .
432
        StringBuilder builder = new StringBuilder();
433
        builder.append("x'");
434
        for (byte abyte : data) {
435
            int v = abyte & 0xff;
436
            builder.append(String.format("%02x", v));
437
        }
438
        builder.append("'");
439
        return builder.toString();
440
    }
441

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

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

    
590
    @Override
591
    protected AlterTableBuilder createAlterTableBuilder() {
592
        return new H2SpatialAlterTableBuilderBase();
593
    }
594
            
595
    @Override
596
    protected TableNameBuilder createTableNameBuilder() {
597
        return new H2SpatialTableNameBuilderBase();
598
    }
599
    
600
    @Override
601
    protected CreateTableBuilder createCreateTableBuilder() {
602
        return new H2SpatialCreateTableBuilder();
603
    }
604

    
605
    @Override
606
    protected SelectBuilder createSelectBuilder() {
607
        return new H2SpatialSelectBuilderBase();
608
    }
609

    
610
    @Override
611
    protected CreateIndexBuilder createCreateIndexBuilder() {
612
        return new H2SpatialCreateIndexBuilder();
613
    }
614

    
615
}