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

History | View | Annotate | Download (20.6 KB)

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

    
3
import java.sql.PreparedStatement;
4
import java.text.MessageFormat;
5
import java.util.ArrayList;
6
import java.util.Date;
7
import java.util.List;
8
import org.apache.commons.lang3.tuple.Pair;
9
import org.gvsig.fmap.dal.DataTypes;
10
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
11
import org.gvsig.fmap.dal.feature.FeatureType;
12
import org.gvsig.fmap.dal.feature.spi.FeatureProvider;
13
import org.gvsig.fmap.dal.store.jdbc2.spi.JDBCSQLBuilderBase;
14
import org.gvsig.fmap.geom.Geometry;
15
import org.gvsig.tools.dispose.Disposable;
16

    
17
public class H2SpatialSQLBuilder extends JDBCSQLBuilderBase {
18

    
19
    private final H2SpatialHelper helper;
20

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

    
44
        config.remove_functionality(SQLConfig.DELETE_GEOMETRY_COLUMN_FROM_TABLE_schema_table);
45
        config.remove_functionality(SQLConfig.DELETE_GEOMETRY_COLUMN_FROM_TABLE_table);
46
         
47
//        config.set(SQLConfig.UPDATE_TABLE_STATISTICS_table,"ANALYZE TABLE {0} SAMPLE_SIZE 0");
48
        config.set(SQLConfig.UPDATE_TABLE_STATISTICS_table,"ANALYZE SAMPLE_SIZE 0");
49
        config.set(SQLConfig.CREATE_INDEX_name_ON_table_column, "CREATE INDEX IF NOT EXISTS {0} ON {1} (\"{2}\")");
50
        config.set(SQLConfig.CREATE_INDEX_name_ON_table_USING_GIST_column, "CREATE SPATIAL INDEX IF NOT EXISTS {0} ON {1} (\"{2}\")");
51

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

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

    
87
    public class H2SpatialTableNameBuilderBase extends TableNameBuilderBase {
88

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

    
94
        @Override
95
        public boolean has_database() {
96
            return false;
97
        }
98
        
99
    }
100
    
101
    protected class H2SpatialAlterTableBuilderBase extends AlterTableBuilderBase {
102
        @Override
103
        public List<String> toStrings() {
104
            List<String> sqls = new ArrayList<>();
105
            for (String column : drops) {
106
                StringBuilder builder = new StringBuilder();
107
                builder.append("ALTER TABLE ");
108
                builder.append(this.table.toString());
109
                builder.append(" DROP COLUMN IF EXISTS ");
110
                builder.append(identifier(column)); 
111
                sqls.add(builder.toString());
112
            }
113
            for (ColumnDescriptorBuilderBase column : adds) {
114
                StringBuilder builder = new StringBuilder();
115
                builder.append("ALTER TABLE ");
116
                builder.append(this.table.toString());
117
                builder.append(" ADD COLUMN ");
118
                builder.append(identifier(column.getName())); 
119
                builder.append(" ");
120
                if( column.getType() == DataTypes.INT && column.isAutomatic() ) {
121
                    builder.append(" SERIAL");
122
                } else {
123
                    builder.append(
124
                        sqltype(
125
                            column.getType(), 
126
                            column.getPrecision(), 
127
                            column.getSize(),
128
                            column.getGeometryType(), 
129
                            column.getGeometrySubtype()
130
                        )
131
                    );
132
                }
133
                if (column.getDefaultValue() == null) {
134
                    if (column.allowNulls()) {
135
                        builder.append(" DEFAULT NULL");
136
                    }
137
                } else {
138
                    builder.append(" DEFAULT '");
139
                    builder.append(column.getDefaultValue().toString());
140
                    builder.append("'");
141
                }
142
                if (column.allowNulls()) {
143
                    builder.append(" NULL");
144
                } else {
145
                    builder.append(" NOT NULL");
146
                }
147
                if (column.isPrimaryKey()) {
148
                    builder.append(" PRIMARY KEY");
149
                }
150
                sqls.add(builder.toString());
151
                if( column.isIndexed() ) {
152
                    String sql;
153
                    String name = "idx_" + this.table().getName() + "_" + column.getName();
154
                    if( column.isGeometry() ) {
155
                        sql = MessageFormat.format(
156
                            config.getString(SQLConfig.CREATE_INDEX_name_ON_table_USING_GIST_column),
157
                            name,
158
                            this.table().toString(),
159
                            column.getName()
160
                        );
161
                    } else {
162
                        sql = MessageFormat.format(
163
                            config.getString(SQLConfig.CREATE_INDEX_name_ON_table_column),
164
                            name,
165
                            this.table().toString(),
166
                            column.getName()
167
                        );
168
                    }
169
                    sqls.add(sql);
170
                }
171
            }
172
            for (ColumnDescriptorBuilderBase column : alters) {
173
                StringBuilder builder = new StringBuilder();
174
                builder.append("ALTER TABLE ");
175
                builder.append(this.table.toString());
176
                builder.append(" ALTER COLUMN ");
177
                builder.append(identifier(column.getName())); 
178
                builder.append(" ");
179
                builder.append(
180
                    sqltype(
181
                        column.getType(), 
182
                        column.getPrecision(), 
183
                        column.getSize(),
184
                        column.getGeometryType(), 
185
                        column.getGeometrySubtype()
186
                    )
187
                );
188
                if (column.getDefaultValue() == null) {
189
                    if (column.allowNulls()) {
190
                        builder.append(" DEFAULT NULL");
191
                    }
192
                } else {
193
                    builder.append(" DEFAULT '");
194
                    builder.append(column.getDefaultValue().toString());
195
                    builder.append("'");
196
                }
197
                if( column.isAutomatic() ) {
198
                    builder.append(" AUTO_INCREMENT");
199
                }
200
                sqls.add(builder.toString());
201
                if( column.isIndexed() ) {
202
                    String sql;
203
                    String name = "idx_" + this.table().getName() + "_" + column.getName();
204
                    if( column.isGeometry() ) {
205
                        sql = MessageFormat.format(
206
                            config.getString(SQLConfig.CREATE_INDEX_name_ON_table_USING_GIST_column),
207
                            name,
208
                            this.table().toString(),
209
                            column.getName()
210
                        );
211
                    } else {
212
                        sql = MessageFormat.format(
213
                            config.getString(SQLConfig.CREATE_INDEX_name_ON_table_column),
214
                            name,
215
                            this.table().toString(),
216
                            column.getName()
217
                        );
218
                    }
219
                    sqls.add(sql);
220
                }
221
            }
222
            for (Pair<String,String> pair : renames) {
223
                StringBuilder builder = new StringBuilder();
224
                builder.append("ALTER TABLE ");
225
                builder.append(this.table.toString());
226
                builder.append(" RENAME COLUMN ");
227
                builder.append(identifier(pair.getLeft())); 
228
                builder.append(" TO ");
229
                builder.append(identifier(pair.getRight())); 
230
                sqls.add(builder.toString());
231
            }
232
            return sqls;
233
        }
234

    
235
    }
236
    
237
    protected class H2SpatialCreateTableBuilder extends CreateTableBuilderBase {
238

    
239
        @Override
240
        public List<String> toStrings() {
241

    
242
            List<String> sqls = new ArrayList<>();
243
            StringBuilder builder = new StringBuilder();
244

    
245
            builder.append("CREATE TABLE ");
246
            builder.append(this.table.toString());
247
            builder.append(" (");
248
            boolean first = true;
249
            for (ColumnDescriptorBuilder column : columns) {
250

    
251
                if (first) {
252
                    first = false;
253
                } else {
254
                    builder.append(", ");
255
                }
256
                builder.append(identifier(column.getName()));
257
                builder.append(" ");
258
                builder.append(
259
                    sqltype(
260
                        column.getType(), 
261
                        column.getPrecision(), 
262
                        column.getSize(),
263
                        column.getGeometryType(), 
264
                        column.getGeometrySubtype()
265
                    )
266
                );
267
                if( !column.isGeometry() ) {
268
                    if (column.isPrimaryKey()) {
269
                        builder.append(" PRIMARY KEY");
270
                    }
271
                    if( column.isAutomatic() ) {
272
                        builder.append(" AUTO_INCREMENT");
273
                    }
274
                    if (column.getDefaultValue() == null) {
275
                        if (column.allowNulls()) {
276
                            builder.append(" DEFAULT NULL");
277
                        }
278
                    } else {
279
                        if( column.getType() == DataTypes.DATE ) {
280
                            builder.append(" DEFAULT ( TIMESTAMP '");
281
                            Date d = (Date) column.getDefaultValue();
282
                            builder.append(MessageFormat.format( "{0,date,yyyy-MM-dd HH:mm:ss.S}",d));
283
                            builder.append("' )");
284
                        } else {
285
                            builder.append(" DEFAULT '");
286
                            builder.append(column.getDefaultValue().toString());
287
                            builder.append("'");
288
                        }
289
                    }
290
                }
291
                if (!column.allowNulls()) {
292
                    builder.append(" NOT NULL");
293
                }
294
            }
295
            builder.append(" )");
296
            sqls.add(builder.toString());
297
            for (ColumnDescriptorBuilderBase column : columns) {
298
                if( column.isIndexed() ) {
299
                    String sql;
300
                    String name = "idx_" + this.table().getName() + "_" + column.getName();
301
                    if( column.isGeometry() ) {
302
                        sql = MessageFormat.format(
303
                            config.getString(SQLConfig.CREATE_INDEX_name_ON_table_USING_GIST_column),
304
                            name,
305
                            this.table().toString(),
306
                            column.getName()
307
                        );
308
                    } else {
309
                        sql = MessageFormat.format(
310
                            config.getString(SQLConfig.CREATE_INDEX_name_ON_table_column),
311
                            name,
312
                            this.table().toString(),
313
                            column.getName()
314
                        );
315
                    }
316
                    sqls.add(sql);
317
                }
318
            }            
319
            
320
            return sqls;
321
        }
322
    }
323

    
324
    public class H2SpatialSelectBuilderBase extends SelectBuilderBase {
325
        
326
        @Override
327
        protected boolean isValid(StringBuilder message) {
328
            if( message == null ) {
329
                message = new StringBuilder();
330
            }
331
            if( this.has_offset() && !this.has_order_by() ) {
332
                // Algunos gestores de BBDD requieren que se especifique un
333
                // orden para poder usar OFFSET. Como eso parece buena idea para
334
                // asegurar que siempre tengamos los mismo resultados, lo exijimos
335
                // siempre.
336
                message.append("Can't use OFFSET without an ORDER BY.");
337
                return false;
338
            }
339
            return true;
340
        }        
341
        
342
        @Override
343
        public String toString() {
344
            StringBuilder builder = new StringBuilder();
345
            if( !isValid(builder) ) {
346
                throw new IllegalStateException(builder.toString());
347
            }
348
            builder.append("SELECT ");
349
            if( this.distinct ) {
350
                builder.append("DISTINCT ");
351
            }
352
            boolean first = true;
353
            for (SelectColumnBuilder column : columns) {
354
                if (first) {
355
                    first = false;
356
                } else {
357
                    builder.append(", ");
358
                }
359
                builder.append(column.toString());
360
            }
361

    
362
            if ( this.has_from() ) {
363
                builder.append(" FROM ");
364
                builder.append(this.from.toString());
365
            }
366
            if ( this.has_where() ) {
367
                builder.append(" WHERE ");
368
                builder.append(this.where.toString());
369
            }
370
            
371
            if( this.has_order_by() ) {
372
                builder.append(" ORDER BY ");
373
                first = true;
374
                for (OrderByBuilder item : this.order_by) {
375
                    if (first) {
376
                        first = false;
377
                    } else {
378
                        builder.append(", ");
379
                    }
380
                    builder.append(item.toString());                    
381
                }   
382
            }
383
            
384
            if ( this.has_limit() && this.has_offset() ) {
385
                builder.append(" LIMIT ");
386
                builder.append(this.limit);
387
                builder.append(" OFFSET ");
388
                builder.append(this.offset);
389
                
390
            } else if ( this.has_limit()) {
391
                builder.append(" LIMIT ");
392
                builder.append(this.limit);
393

    
394
            } else if ( this.has_offset() ) {
395
                builder.append(" LIMIT -1 OFFSET ");
396
                builder.append(this.offset);    
397
            }
398
            return builder.toString();
399

    
400
        }
401
    }
402

    
403
    @Override
404
    public String bytearray(byte[] data) {
405
        // H2Spatial usa un formato diferencte para especificar un array de 
406
        // bytes. En lugar de 0x... usa x'...' .
407
        StringBuilder builder = new StringBuilder();
408
        builder.append("x'");
409
        for (byte abyte : data) {
410
            int v = abyte & 0xff;
411
            builder.append(String.format("%02x", v));
412
        }
413
        builder.append("'");
414
        return builder.toString();
415
    }
416

    
417
    @Override
418
    public Object sqlgeometrydimension(int type, int subtype) {
419
        //'XY' or 2: 2D points, identified by X and Y coordinates
420
        //'XYM': 2D points, identified by X and Y coordinates. Each point stores an M-value (measure) as well
421
        //'XYZ' or 3: 3D points, identified by X, Y and Z coordinates
422
        //'XYZM' or 4: 3D points, identified by X, Y and Z coordinates. Each point stores an M-value (measure) as well
423
        switch(subtype) {
424
            case Geometry.SUBTYPES.GEOM2D:
425
            default:
426
                return "XY";
427
            case Geometry.SUBTYPES.GEOM2DM:
428
                return "XYM";
429
            case Geometry.SUBTYPES.GEOM3D:
430
                return "XYZ";
431
            case Geometry.SUBTYPES.GEOM3DM:
432
                return "XYZM";
433
        }
434
    }
435

    
436
    @Override
437
    public String sqltype(int type, int p, int s, int geomtype, int geomSubtype) {
438
        if( type!=DataTypes.GEOMETRY ) {
439
            return super.sqltype(type, p, s, geomtype, geomSubtype);
440
        }
441
        //
442
        // https://github.com/orbisgis/h2gis/wiki/1.-Spatial-data#geometry-columns-view
443
        //
444
        switch(geomtype) {
445
        case Geometry.TYPES.POINT:
446
            return "POINT";
447
        case Geometry.TYPES.MULTIPOINT:
448
            return "MULTIPOINT";
449
        case Geometry.TYPES.LINE:
450
            return "LINESTRING";
451
        case Geometry.TYPES.MULTILINE:
452
            return "MULTILINESTRING";
453
        case Geometry.TYPES.POLYGON:
454
            return "POLYGON";
455
        case Geometry.TYPES.MULTIPOLYGON:
456
            return "MULTIPOLYGON";
457
        default:
458
            return "GEOMETRY";
459
        }
460
    }
461
    
462
    public H2SpatialHelper getHelper() {
463
        return this.helper;
464
    }
465
    
466
    @Override
467
    public Disposable setParameters(PreparedStatement st, FeatureProvider feature) {
468
        try {
469
            FeatureType featureType = feature.getType();
470
            List<Object> values = new ArrayList<>();
471
            for (Parameter parameter : this.getParameters()) {
472
                if (parameter.is_constant()) {
473
                    values.add(parameter.getValue());
474
                } else {
475
                    String name = parameter.getName();
476
                    FeatureAttributeDescriptor descriptor = featureType.getAttributeDescriptor(name);
477
                    switch( descriptor.getType() ) {
478
                    case DataTypes.DATE:
479
                        Date value = (Date)(feature.get(name));
480
                        if( value == null ) {
481
                            values.add(null);
482
                        } else {
483
                            values.add(value.getTime());
484
                        }
485
                        break;
486
                    case DataTypes.GEOMETRY:
487
                        Geometry geom = this.getHelper().forceGeometryType(
488
                            descriptor.getGeomType(),
489
                            (Geometry)(feature.get(name))
490
                        );
491
                        values.add(geom);
492
                        break;
493
                    default:
494
                        values.add(feature.get(name));
495
                        break;
496
                    }
497
                }
498
            }
499
            return this.setStatementParameters(st, values, this.geometry_support_type());
500
        } catch (Exception ex) {
501
            String f = "unknow";
502
            try {
503
                f = feature.toString();
504
            } catch (Exception ex2) {
505
                // Do nothing
506
            }
507
            throw new RuntimeException("Can't set parameters to prepared statement from the feature (" + f + ")", ex);
508
        }
509
    }
510

    
511
    @Override
512
    protected AlterTableBuilder createAlterTableBuilder() {
513
        return new H2SpatialAlterTableBuilderBase();
514
    }
515
            
516
    @Override
517
    protected TableNameBuilder createTableNameBuilder() {
518
        return new H2SpatialTableNameBuilderBase();
519
    }
520
    
521
    @Override
522
    protected CreateTableBuilder createCreateTableBuilder() {
523
        return new H2SpatialCreateTableBuilder();
524
    }
525

    
526
    @Override
527
    protected SelectBuilder createSelectBuilder() {
528
        return new H2SpatialSelectBuilderBase();
529
    }
530

    
531
}