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

History | View | Annotate | Download (20.2 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.Parameter;
16
import org.gvsig.expressionevaluator.Formatter;
17
import org.gvsig.fmap.dal.DataTypes;
18
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
19
import org.gvsig.fmap.dal.feature.FeatureType;
20
import org.gvsig.fmap.dal.feature.spi.FeatureProvider;
21
import org.gvsig.fmap.dal.store.jdbc2.spi.JDBCSQLBuilderBase;
22
import org.gvsig.fmap.geom.Geometry;
23
import org.gvsig.fmap.geom.exception.CreateGeometryException;
24
import org.gvsig.sqlite.dal.expressionbuilderformatter.SQLiteFormatter;
25
import org.gvsig.sqlite.dal.geopackage.GeopackageGeometryBuilder;
26
import org.gvsig.sqlite.dal.geopackage.GeopackageGeometryColumns;
27
import org.gvsig.sqlite.dal.geopackage.GeopackageUtils;
28
import org.gvsig.sqlite.dal.geopackage.index.GeopackageIndexRTree;
29
import org.gvsig.sqlite.dal.utils.TemplateUtils;
30
import org.gvsig.tools.dataTypes.DataTypeUtils;
31
import org.gvsig.tools.dispose.Disposable;
32
import org.sqlite.SQLiteConnection;
33

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

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

    
106
    public class SQLiteTableNameBuilderBase extends TableNameBuilderBase {
107

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

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

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

    
210
            for (ColumnDescriptor column : columns) {
211
                if (column.isGeometry()) {
212
                    sqls.add(GeopackageGeometryColumns.buildInsertSQL(
213
                            this.table.getName(), 
214
                            column.getName(), 
215
                            column.getGeometryType(), 
216
                            column.getGeometrySubtype(),
217
                            column.getGeometrySRSId()
218
                    ));
219
                    
220
                    //TODO: A?adir el insert a gpkg_extensions
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
            String sql;
250
            sql = MessageFormat.format(
251
                    STMT_DELETE_GEOMETRY_COLUMN_FROM_TABLE_table,
252
                    as_identifier(this.table.getName())
253
            );
254
            if (!StringUtils.isEmpty(sql)) {
255
                sqls.add(sql);
256
            }
257
            
258
            sqls.add(String.format(
259
                    "DELETE FROM \"main\".\"gpkg_contents\" WHERE \"identifier\" = '%s'",
260
                    this.table.getName()
261
            ));
262

    
263
            sqls.add(String.format(
264
                    "DELETE FROM \"main\".\"gpkg_extensions\" WHERE \"table_name\" = '%s'",
265
                    this.table.getName()
266
            ));
267

    
268
            sqls.add(
269
                    MessageFormat.format(
270
                            STMT_DROP_TABLE_table,
271
                            this.table.toString(formatter)
272
                    )
273
            );
274

    
275
            return sqls;
276
        }
277
    }
278

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

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

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

    
361
            } else if ( this.has_offset() ) {
362
                builder.append(" LIMIT -1 OFFSET ");
363
                builder.append(this.offset);    
364
            }
365
            return builder.toString();
366

    
367
        }
368
    }
369

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

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

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

    
483
    
484
            
485
    @Override
486
    public TableNameBuilder createTableNameBuilder() {
487
        return new SQLiteTableNameBuilderBase();
488
    }
489
    
490
    protected DropTableBuilder createDropTableBuilder() {
491
        return new SQLiteDropTableBuilder();
492
    }
493

    
494
    
495
    @Override
496
    protected CreateTableBuilder createCreateTableBuilder() {
497
        return new SQLiteCreateTableBuilder();
498
    }
499

    
500
    @Override
501
    public SelectBuilder createSelectBuilder() {
502
        return new SQLiteSelectBuilderBase();
503
    }
504

    
505
    @Override
506
    public Formatter formatter() {
507
        if( this.formatter==null ) {
508
            this.formatter = new SQLiteFormatter(this);
509
        }
510
        return this.formatter;
511
    }    
512
}