Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.plugin / org.gvsig.sqlite / org.gvsig.sqlite.provider / src / main / java / org / gvsig / sqlite / dal / SQLiteSQLBuilder.java @ 47580

History | View | Annotate | Download (20.3 KB)

1
package org.gvsig.sqlite.dal;
2

    
3
import java.sql.Connection;
4
import java.sql.PreparedStatement;
5
import java.sql.SQLException;
6
import java.sql.Time;
7
import java.sql.Timestamp;
8
import java.text.MessageFormat;
9
import java.time.LocalDateTime;
10
import java.time.format.DateTimeFormatter;
11
import java.util.ArrayList;
12
import java.util.Date;
13
import java.util.List;
14
import org.apache.commons.lang3.StringUtils;
15
import org.gvsig.expressionevaluator.ExpressionBuilder;
16
import org.gvsig.expressionevaluator.ExpressionBuilder.Parameter;
17
import org.gvsig.expressionevaluator.Formatter;
18
import org.gvsig.fmap.dal.DataTypes;
19
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
20
import org.gvsig.fmap.dal.feature.FeatureType;
21
import org.gvsig.fmap.dal.feature.spi.FeatureProvider;
22
import org.gvsig.fmap.dal.store.jdbc2.spi.JDBCSQLBuilderBase;
23
import org.gvsig.fmap.geom.Geometry;
24
import org.gvsig.fmap.geom.exception.CreateGeometryException;
25
import org.gvsig.sqlite.dal.expressionbuilderformatter.SQLiteFormatter;
26
import org.gvsig.sqlite.dal.geopackage.GeopackageGeometryBuilder;
27
import org.gvsig.sqlite.dal.geopackage.GeopackageGeometryColumns;
28
import org.gvsig.sqlite.dal.geopackage.GeopackageUtils;
29
import org.gvsig.sqlite.dal.geopackage.index.GeopackageIndexRTree;
30
import org.gvsig.sqlite.dal.utils.TemplateUtils;
31
import org.gvsig.tools.dataTypes.DataTypeUtils;
32
import org.gvsig.tools.dispose.Disposable;
33
import org.sqlite.SQLiteConnection;
34

    
35
public class SQLiteSQLBuilder extends JDBCSQLBuilderBase {
36
    
37
    protected Formatter formatter = null;
38
        
39
    public SQLiteSQLBuilder(SQLiteHelper helper) {
40
        super(helper);      
41
        //
42
        // SQLite 4.3.0, SQL functions reference list
43
        //
44
        // http://www.gaia-gis.it/gaia-sins/spatialite-sql-4.3.0.html
45
        //
46
        // https://sqlite.org/lang.html
47
        //
48
        
49
        this.defaultSchema = "";
50
        this.supportSchemas = false;
51
        this.allowAutomaticValues = true;
52
        this.geometrySupportType = this.helper.getGeometrySupportType();
53
        this.hasSpatialFunctions = this.helper.hasSpatialFunctions();
54
        this.expressionBuilder.geometry_support_type(this.geometrySupportType);
55

    
56
        this.STMT_DELETE_GEOMETRY_COLUMN_FROM_TABLE_schema_table = null; //"DELETE FROM gpkg_geometry_columns WHERE table_name = {1}";
57
        this.STMT_DELETE_GEOMETRY_COLUMN_FROM_TABLE_table = "DELETE FROM gpkg_geometry_columns WHERE table_name = {0}";
58
         
59
        this.STMT_UPDATE_TABLE_STATISTICS_table = "ANALYZE {0}";
60
        
61
        this.type_boolean = "BOOLEAN";
62
        this.type_byte = "TINYINT";
63
        this.type_bytearray = "BLOB";
64
        this.type_geometry = "BLOB";
65
        this.type_char = "CHARACTER(1)";
66
        this.type_date = "DATE";
67
        this.type_double = "DOUBLE PRECISION"; 
68
        this.type_decimal_ps = "DECIMAL({0,Number,##########},{1,Number,##########})";
69
        this.type_decimal_p = "DECIMAL({0,Number,##########})";
70
        this.type_float = "FLOAT";
71
        this.type_int = "MEDIUMINT";
72
        this.type_long = "BIGINT";
73
        this.type_string = "TEXT";
74
        this.type_string_p = "VARCHAR({0,Number,#######})";
75
        this.type_time = "TIME";
76
        this.type_timestamp = "DATETIME";
77
        this.type_version = "VARCHAR(30)";
78
        this.type_URI = "TEXT";
79
        this.type_URL = "TEXT";
80
        this.type_FILE = "TEXT";
81
        this.type_FOLDER = "TEXT";           
82
        
83
//        config.set(SQLConfig.ST_ExtentAggregate, "(1)");
84
//        config.set(SQLConfig.ST_UnionAggregate, "(1)");
85
//        config.set(SQLConfig.ST_ExtentAggregate, "(1)");        
86
//        config.set(SQLConfig.ST_UnionAggregate, "(1)");
87
//        config.set(SQLConfig.Find_SRID, "(1)");
88
//        config.set(SQLConfig.ST_SRID, "(1)");
89
//        config.set(SQLConfig.ST_Contains, "(1)");
90
//        config.set(SQLConfig.ST_Crosses, "(1)");
91
//        config.set(SQLConfig.ST_Disjoint, "(1)");
92
//        config.set(SQLConfig.ST_Equals, "(1)");
93
//        config.set(SQLConfig.ST_IsClosed, "(1)");
94
//        config.set(SQLConfig.ST_Overlaps, "(1)");
95
//        config.set(SQLConfig.ST_Touches, "(1)");
96
//        config.set(SQLConfig.ST_Within, "(1)");
97
//        config.set(SQLConfig.ST_Intersects, "(1)");
98
//        config.set(SQLConfig.ST_Simplify, "(1)");
99
//        config.set(SQLConfig.ST_Within, "(1)");
100
//        config.set(SQLConfig.ST_GeomFromWKB, "({0})");
101
//        config.set(SQLConfig.ST_AsBinary, "({0})");
102
//        config.set(SQLConfig.ST_AsEWKB, "({0})");
103
//        config.set(SQLConfig.ST_AsText, "({0})");
104
  
105
    }
106

    
107
    public class SQLiteTableNameBuilderBase extends TableNameBuilderBase {
108

    
109
        @Override
110
        public boolean has_schema() {
111
            return false;
112
        }
113

    
114
        @Override
115
        public boolean has_database() {
116
            return false;
117
        }
118
        
119
    }
120
    
121
    protected class SQLiteCreateTableBuilder extends CreateTableBuilderBase {
122

    
123
        @Override
124
        public List<String> toStrings(Formatter formatter) {
125
            //
126
            // Respecto al base cambia la declaracion de campo automaticos
127
            // SQLite usa AUTOINCREMENT en lugar de SERIAL y que hay que usar
128
            // la funcion AddGeometryColumn para a?adir las columnas de tipo
129
            // geometria.
130
            // El orden en el que hay que declarar las constrains tambien cambia
131
            // respecto al que hay por defecto.
132
            //
133
            List<String> sqls = new ArrayList<>();
134
            
135
            sqls.add("CREATE TABLE IF NOT EXISTS gpkg_contents (table_name TEXT NOT NULL PRIMARY KEY,data_type TEXT NOT NULL,identifier TEXT UNIQUE,description TEXT DEFAULT '',last_change DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),min_x DOUBLE, min_y DOUBLE,max_x DOUBLE, max_y DOUBLE,srs_id INTEGER,CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys(srs_id))");
136
            if(this.hasGeometry()) {
137
                sqls.add(TemplateUtils.getSqlTemplate(GeopackageIndexRTree.GROUP_NAME, "create_table_gpkg_extensions"));
138
                sqls.add(GeopackageGeometryColumns.buildCreateTableSQLIfNotExists());
139
                sqls.add(String.format(
140
                        "INSERT INTO \"gpkg_contents\" (\"table_name\", \"data_type\", \"identifier\", \"description\", \"last_change\", \"min_x\", \"min_y\", \"max_x\", \"max_y\", \"srs_id\") VALUES ('%s', '%s', '%s', '', '%s', NULL, NULL, NULL, NULL, NULL)",
141
                        this.table.getName(),
142
                        "features",
143
                        this.table.getName(),
144
                        new java.sql.Timestamp(new Date().getTime()).toString()
145
                ));
146
            } else {
147
                sqls.add(String.format(
148
                        "INSERT INTO \"gpkg_contents\" (\"table_name\", \"data_type\", \"identifier\", \"description\", \"last_change\", \"min_x\", \"min_y\", \"max_x\", \"max_y\", \"srs_id\") VALUES ('%s', '%s', '%s', '', '%s', NULL, NULL, NULL, NULL, NULL)",
149
                        this.table.getName(),
150
                        "attributes",
151
                        this.table.getName(),
152
                        new java.sql.Timestamp(new Date().getTime()).toString()
153
                ));
154
                
155
            }
156
            
157
            
158
            StringBuilder builder = new StringBuilder();
159
            String pkName = null;
160
            builder.append("CREATE TABLE ");
161
            builder.append(this.table.toString(formatter));
162
            builder.append(" (");
163
            boolean first = true;
164
            for (ColumnDescriptor column : columns) {
165
                if (first) {
166
                    first = false;
167
                } else {
168
                    builder.append(", ");
169
                }
170
                builder.append(as_identifier(column.getName()));
171
                builder.append(" ");
172
                builder.append(
173
                    sqltype(
174
                        column.getType(), 
175
                        column.getSize(),
176
                        column.getPrecision(), 
177
                        column.getScale(), 
178
                        column.getGeometryType(), 
179
                        column.getGeometrySubtype()
180
                    )
181
                );
182
                if (column.isPrimaryKey()) {
183
                    builder.append(" PRIMARY KEY");
184
                    pkName = column.getName();
185
                }
186
                if( column.isAutomatic() ) {
187
                    builder.append(" AUTOINCREMENT");
188
                }
189
                if (column.getDefaultValue() == null) {
190
                    if (column.allowNulls()) {
191
                        builder.append(" DEFAULT NULL");
192
                    }
193
                } else {
194
                    if( column.getType() == DataTypes.DATE ) {
195
                        builder.append(" DEFAULT ( date('");
196
                        builder.append(column.getDefaultValue().toString());
197
                        builder.append("') )");
198
                    } else {
199
                        builder.append(" DEFAULT '");
200
                        builder.append(column.getDefaultValue().toString());
201
                        builder.append("'");
202
                    }
203
                }
204
                if (!column.allowNulls()) {
205
                    builder.append(" NOT NULL");
206
                }
207
            }
208
            builder.append(" )");
209
            sqls.add(builder.toString());
210

    
211
            for (ColumnDescriptor column : columns) {
212
                if (column.isGeometry()) {
213
                    sqls.add(GeopackageGeometryColumns.buildInsertSQL(
214
                            this.table.getName(), 
215
                            column.getName(), 
216
                            column.getGeometryType(), 
217
                            column.getGeometrySubtype(),
218
                            column.getGeometrySRSId()
219
                    ));
220
                    
221
                    GeopackageIndexRTree index = new GeopackageIndexRTree();
222
                    sqls.addAll(index.getCreateIndexSQL(this.table.getName(), column.getName(), pkName));
223
                    
224
                }
225
            }
226

    
227
            
228

    
229
            
230
            return sqls;
231
        }
232

    
233
        private boolean hasGeometry() {
234
            for (ColumnDescriptor column : columns) {
235
                if (column.isGeometry()) {
236
                    return true;
237
                }
238
            }
239
            return false;
240
        }
241
    }
242

    
243
    protected class SQLiteDropTableBuilder extends DropTableBuilderBase {
244

    
245
        @Override
246
        public List<String> toStrings(Formatter formatter) {
247
            List<String> sqls = new ArrayList<>();
248
            
249
            //No se pueden borrar aqui los indices espaciales, se hace en el explorer.remove
250
//            GeopackageIndexRTree index = new GeopackageIndexRTree();
251
//            sqls.addAll(index.getRemoveIndexSQL(this.table.getName(), column.getName(), pkName));
252
            
253
            sqls.add(GeopackageGeometryColumns.buildDeleteSQL(this.table.getName()));
254
            
255
            sqls.add(String.format(
256
                    "DELETE FROM \"gpkg_contents\" WHERE \"identifier\" = '%s'",
257
                    this.table.getName()
258
            ));
259

    
260
            sqls.add(String.format(
261
                    "DELETE FROM \"gpkg_extensions\" WHERE \"table_name\" = '%s'",
262
                    this.table.getName()
263
            ));
264
            
265
            sqls.add(
266
                    MessageFormat.format(
267
                            STMT_DROP_TABLE_table,
268
                            this.table.toString(formatter)
269
                    )
270
            );
271

    
272
            return sqls;
273
        }
274
    }
275

    
276
    public class SQLiteSelectBuilderBase extends SelectBuilderBase {
277
        
278
        @Override
279
        protected boolean isValid(StringBuilder message) {
280
            if( message == null ) {
281
                message = new StringBuilder();
282
            }
283
            if( this.has_offset() && !this.has_order_by() ) {
284
                // Algunos gestores de BBDD requieren que se especifique un
285
                // orden para poder usar OFFSET. Como eso parece buena idea para
286
                // asegurar que siempre tengamos los mismo resultados, lo exijimos
287
                // siempre.
288
                message.append("Can't use OFFSET without an ORDER BY.");
289
                return false;
290
            }
291
            return true;
292
        }        
293
        
294
        @Override
295
        public String toString(Formatter formatter) {
296
            // SQLite requiere que si se especifica OFFSET deba especificarse
297
            // LIMIT obligatoriamente. Se le puede poner un LIMIT -1 cuando 
298
            // queramos un OFFSET sin especificar un LIMIT.
299
            StringBuilder builder = new StringBuilder();
300
            if( !isValid(builder) ) {
301
                throw new IllegalStateException(builder.toString());
302
            }
303
            builder.append("SELECT ");
304
            if( this.distinct ) {
305
                builder.append("DISTINCT ");
306
            }
307
            boolean first = true;
308
            for (SelectColumnBuilder column : columns) {
309
                if (first) {
310
                    first = false;
311
                } else {
312
                    builder.append(", ");
313
                }
314
                builder.append(column.toString(formatter));
315
            }
316

    
317
            if ( this.has_from() ) {
318
                builder.append(" FROM ");
319
                builder.append(this.from.toString(formatter));
320
            }
321
            if ( this.has_where() ) {
322
                builder.append(" WHERE ");
323
                builder.append(this.where.toString(formatter));
324
            }
325
            
326
            if( this.has_group_by() ) {
327
                builder.append(" GROUP BY ");
328
                builder.append(this.groupColumn.get(0).toString(formatter));
329
                for (int i = 1; i < groupColumn.size(); i++) {
330
                    builder.append(", ");
331
                    builder.append(this.groupColumn.get(i).toString(formatter));
332
                }
333
            }
334

    
335
            if( this.has_order_by() ) {
336
                builder.append(" ORDER BY ");
337
                first = true;
338
                for (OrderByBuilder item : this.order_by) {
339
                    if (first) {
340
                        first = false;
341
                    } else {
342
                        builder.append(", ");
343
                    }
344
                    builder.append(item.toString(formatter));                    
345
                }   
346
            }
347
            
348
            if ( this.has_limit() && this.has_offset() ) {
349
                builder.append(" LIMIT ");
350
                builder.append(this.limit);
351
                builder.append(" OFFSET ");
352
                builder.append(this.offset);
353
                
354
            } else if ( this.has_limit()) {
355
                builder.append(" LIMIT ");
356
                builder.append(this.limit);
357

    
358
            } else if ( this.has_offset() ) {
359
                builder.append(" LIMIT -1 OFFSET ");
360
                builder.append(this.offset);    
361
            }
362
            return builder.toString();
363

    
364
        }
365
    }
366

    
367
//    @Override
368
//    public String bytearray(byte[] data) {
369
//        // SQLite usa un formato diferencte para especificar un array de 
370
//        // bytes. En lugar de 0x... usa x'...' .
371
//        StringBuilder builder = new StringBuilder();
372
//        builder.append("x'");
373
//        for (byte abyte : data) {
374
//            int v = abyte & 0xff;
375
//            builder.append(String.format("%02x", v));
376
//        }
377
//        builder.append("'");
378
//        return builder.toString();
379
//    }
380

    
381
    @Override
382
    public Object sqlgeometrydimension(int type, int subtype) {
383
        //'XY' or 2: 2D points, identified by X and Y coordinates
384
        //'XYM': 2D points, identified by X and Y coordinates. Each point stores an M-value (measure) as well
385
        //'XYZ' or 3: 3D points, identified by X, Y and Z coordinates
386
        //'XYZM' or 4: 3D points, identified by X, Y and Z coordinates. Each point stores an M-value (measure) as well
387
        switch(subtype) {
388
            case Geometry.SUBTYPES.GEOM2D:
389
            default:
390
                return "XY";
391
            case Geometry.SUBTYPES.GEOM2DM:
392
                return "XYM";
393
            case Geometry.SUBTYPES.GEOM3D:
394
                return "XYZ";
395
            case Geometry.SUBTYPES.GEOM3DM:
396
                return "XYZM";
397
        }
398
    }
399

    
400
    @Override
401
    public SQLiteHelper getHelper() {
402
        return (SQLiteHelper) this.helper;
403
    }
404
    
405
    @Override
406
    public Disposable setParameters(PreparedStatement st, FeatureProvider feature) {        
407
        try {
408
            FeatureType featureType = feature.getType();
409
            List<Object> values = new ArrayList<>();
410
            for (Parameter parameter : this.parameters()) {
411
                if (parameter.is_constant()) {
412
                    values.add(parameter.value());
413
                } else {
414
                    String name = parameter.name();
415
                    FeatureAttributeDescriptor descriptor = featureType.getAttributeDescriptor(name);
416
                    switch( descriptor.getType() ) {
417
                    case DataTypes.DATE:
418
                        Date value = (Date)(feature.get(name));
419
                        if( value == null ) {
420
                            values.add(null);
421
                        } else {
422
                            LocalDateTime localDate = DataTypeUtils.toLocalDateTime(value);
423
                            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
424
                            values.add(localDate.format(formatter));
425
                        }
426
                        break;
427
                    case DataTypes.TIME:
428
                        Time time = (Time)(feature.get(name));
429
                        if( time == null ) {
430
                            values.add(null);
431
                        } else {
432
                            LocalDateTime localDate = DataTypeUtils.toLocalDateTime(time);
433
                            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
434
                            values.add(localDate.format(formatter));
435
                        }
436
                        break;
437
                    case DataTypes.TIMESTAMP:
438
                        Timestamp timestamp= (Timestamp)(feature.get(name));
439
                        if( timestamp == null ) {
440
                            values.add(null);
441
                        } else {
442
                            LocalDateTime localDate = DataTypeUtils.toLocalDateTime(timestamp);
443
                            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
444
                            values.add(localDate.format(formatter));
445
                        }
446
                        break;
447
                    case DataTypes.GEOMETRY:
448
                        Geometry geom = this.forceGeometryType(
449
                            descriptor.getGeomType(),
450
                            (Geometry)(feature.get(name))
451
                        );
452
                        values.add(geom);
453
                        break;
454
                    default:
455
                        values.add(feature.get(name));
456
                        break;
457
                    }
458
                }
459
            }
460
            return this.setStatementParameters(st, values, this.geometry_support_type());
461
        } catch (SQLException | CreateGeometryException ex) {
462
            String f = "unknow";
463
            try {
464
                f = feature.toString();
465
            } catch (Exception ex2) {
466
                // Do nothing
467
            }
468
            throw new RuntimeException("Can't set parameters to prepared statement from the feature (" + f + ")", ex);
469
        }
470
    }
471
    
472
    @Override
473
    protected byte[] getNativeBytes(Connection conn, Geometry geometry) throws Exception {
474
        GeopackageGeometryBuilder builder = GeopackageUtils.createGeometryBuilder();
475
        builder.setGeometry((SQLiteConnection) conn, geometry);
476
        byte[] bytes = builder.build();
477
        return bytes;
478
    }
479

    
480
    
481
    @Override
482
    public TableNameBuilder createTableNameBuilder() {
483
        return new SQLiteTableNameBuilderBase();
484
    }
485
    
486
    protected DropTableBuilder createDropTableBuilder() {
487
        return new SQLiteDropTableBuilder();
488
    }
489

    
490
    
491
    @Override
492
    protected CreateTableBuilder createCreateTableBuilder() {
493
        return new SQLiteCreateTableBuilder();
494
    }
495

    
496
    @Override
497
    public SelectBuilder createSelectBuilder() {
498
        return new SQLiteSelectBuilderBase();
499
    }
500

    
501
    @Override
502
    public Formatter formatter() {
503
        if( this.formatter==null ) {
504
            this.formatter = new SQLiteFormatter(this);
505
        }
506
        return this.formatter;
507
    }      
508
}