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.jdbc / src / main / java / org / gvsig / fmap / dal / store / jdbc2 / spi / JDBCStoreProviderBase.java @ 45425

History | View | Annotate | Download (23.5 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2020 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 3
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 * For any additional information, do not hesitate to contact us
22
 * at info AT gvsig.com, or visit our website www.gvsig.com.
23
 */
24
package org.gvsig.fmap.dal.store.jdbc2.spi;
25

    
26
import java.text.MessageFormat;
27
import org.gvsig.fmap.dal.store.jdbc2.JDBCStoreProvider;
28
import java.util.Arrays;
29
import java.util.Collections;
30
import java.util.Iterator;
31
import java.util.List;
32
import org.apache.commons.lang3.BooleanUtils;
33
import org.apache.commons.lang3.StringUtils;
34
import org.cresques.cts.IProjection;
35
import org.gvsig.expressionevaluator.Expression;
36
import org.gvsig.fmap.dal.DALLocator;
37
import org.gvsig.fmap.dal.DataManager;
38
import org.gvsig.fmap.dal.DataServerExplorer;
39
import org.gvsig.fmap.dal.DataStore;
40
import org.gvsig.fmap.dal.DataStoreNotification;
41
import org.gvsig.fmap.dal.DataTypes;
42
import org.gvsig.fmap.dal.exception.CloseException;
43
import org.gvsig.fmap.dal.exception.DataException;
44
import org.gvsig.fmap.dal.exception.InitializeException;
45
import org.gvsig.fmap.dal.exception.OpenException;
46
import org.gvsig.fmap.dal.exception.ReadException;
47
import org.gvsig.fmap.dal.feature.EditableFeatureAttributeDescriptor;
48
import org.gvsig.fmap.dal.feature.EditableFeatureType;
49
import org.gvsig.fmap.dal.feature.FeatureQuery;
50
import org.gvsig.fmap.dal.feature.FeatureRule;
51
import org.gvsig.fmap.dal.feature.FeatureRules;
52
import org.gvsig.fmap.dal.feature.FeatureType;
53
import org.gvsig.fmap.dal.feature.spi.AbstractFeatureStoreProvider;
54
import org.gvsig.fmap.dal.feature.spi.FeatureProvider;
55
import org.gvsig.fmap.dal.feature.spi.FeatureReferenceProviderServices;
56
import org.gvsig.fmap.dal.feature.spi.FeatureSetProvider;
57
import org.gvsig.fmap.dal.resource.ResourceParameters;
58
import org.gvsig.fmap.dal.resource.exception.AccessResourceException;
59
import org.gvsig.fmap.dal.resource.exception.ResourceException;
60
import org.gvsig.fmap.dal.resource.spi.AbstractResource;
61
import org.gvsig.fmap.dal.resource.spi.ResourceConsumer;
62
import org.gvsig.fmap.dal.resource.spi.ResourceProvider;
63
import org.gvsig.fmap.dal.spi.DataStoreProviderServices;
64
import org.gvsig.fmap.dal.store.jdbc.JDBCServerExplorerParameters;
65
import org.gvsig.fmap.dal.store.jdbc.JDBCStoreParameters;
66
import org.gvsig.fmap.dal.store.jdbc2.JDBCHelper;
67
import org.gvsig.fmap.dal.store.jdbc2.JDBCUtils;
68
import org.gvsig.fmap.dal.store.jdbc2.OperationsFactory;
69
import org.gvsig.fmap.dal.store.jdbc2.ResulSetControler;
70
import org.gvsig.fmap.dal.store.jdbc2.impl.JDBCSetProvider;
71
import org.gvsig.fmap.dal.store.jdbc2.spi.operations.AppendOperation;
72
import org.gvsig.fmap.dal.store.jdbc2.spi.operations.CalculateEnvelopeOfColumnOperation;
73
import org.gvsig.fmap.dal.store.jdbc2.spi.operations.CanModifyTableOperation;
74
import org.gvsig.fmap.dal.store.jdbc2.spi.operations.CountOperation;
75
import org.gvsig.fmap.dal.store.jdbc2.spi.operations.FetchFeatureProviderByReferenceOperation;
76
import org.gvsig.fmap.dal.store.jdbc2.spi.operations.FetchFeatureTypeOperation;
77
import org.gvsig.fmap.dal.store.jdbc2.spi.operations.PerformChangesOperation;
78
import org.gvsig.fmap.geom.Geometry;
79
import org.gvsig.fmap.geom.primitive.Envelope;
80
import org.gvsig.tools.dynobject.DynField;
81
import org.gvsig.tools.dynobject.DynObject;
82
import org.gvsig.tools.dynobject.exception.DynFieldNotFoundException;
83
import org.gvsig.tools.exception.BaseException;
84
import org.slf4j.Logger;
85
import org.slf4j.LoggerFactory;
86

    
87
@SuppressWarnings("UseSpecificCatch")
88
public class JDBCStoreProviderBase
89
        extends AbstractFeatureStoreProvider
90
        implements ResourceConsumer, JDBCStoreProvider {
91

    
92
    final static private Logger LOGGER = LoggerFactory.getLogger(JDBCStoreProviderBase.class);
93

    
94
    public class CountValue implements CalculatedValue<Long> {
95
        
96
        private Long value = null;
97

    
98
        @Override
99
        public void calculate() {
100
            try {
101
                JDBCStoreParameters params = getParameters();
102
                CountOperation count = getOperations().createCount(
103
                        getFeatureStore().getDefaultFeatureType(),
104
                        getOperations().createTableReference(params),
105
                        params.getBaseFilter(),
106
                        null
107
                );            
108
                this.value = (Long) count.perform();
109
            } catch (DataException ex) {
110
                throw new RuntimeException("Can't calculate count",ex);
111
            }
112
        }
113
        
114
        @Override
115
        public void reset() {
116
            this.value = null;
117
        }
118
        
119
        @Override
120
        public Long get() {
121
            if( this.value == null ) {
122
                this.calculate();
123
            }
124
            return this.value;
125
        }
126
    } 
127
    
128
    public class EnvelopeValue implements CalculatedValue<Envelope> {
129
        
130
        private Envelope value = null;
131
        private boolean needCalculate = true;
132

    
133
        @Override
134
        public void calculate() {
135
            try {
136
                value = null;
137
                String columnName = getFeatureStore()
138
                        .getDefaultFeatureType()
139
                        .getDefaultGeometryAttributeName();
140
                if( columnName==null ) {
141
                    return;
142
                }
143
                IProjection crs = getFeatureStore()
144
                        .getDefaultFeatureType()
145
                        .getDefaultSRS();
146
                JDBCStoreParameters params = getParameters();
147
                CalculateEnvelopeOfColumnOperation calculateEnvelopeOfColumn = 
148
                    getOperations().createCalculateEnvelopeOfColumn(
149
                        getFeatureStore().getDefaultFeatureType(),
150
                        getOperations().createTableReference(params),
151
                        columnName, 
152
                        params.getBaseFilter(), 
153
                        params.getWorkingArea(), 
154
                        crs
155
                    );
156
                value = (Envelope) calculateEnvelopeOfColumn.perform();
157
                
158
            } catch(Exception ex) {
159
                throw new RuntimeException("Can't calculate envelope.", ex);
160
            } finally {
161
               needCalculate = false;
162
            }
163
        }
164
        
165
        @Override
166
        public void reset() {
167
            this.value = null;
168
            this.needCalculate = true;
169
        }
170
        
171
        @Override
172
        public synchronized Envelope get() {
173
            if( needCalculate ) {
174
                this.calculate();
175
            }
176
            return this.value;
177
        }
178
    } 
179
        
180
    public class AllowWriteValue implements CalculatedValue<Boolean> {
181
        
182
        private Boolean value = null;
183

    
184
        @Override
185
        public void calculate() {
186
            try {
187
                JDBCStoreParameters params = getParameters();
188
                CanModifyTableOperation canModifyTable = 
189
                    getOperations().createCanModifyTableOperation(
190
                        getOperations().createTableReference(params)
191
                    );
192
                this.value = (boolean) canModifyTable.perform();
193
            } catch(Exception ex) {
194
                throw new RuntimeException("Can't determine if allow write.", ex);
195
            }
196
        }
197
        
198
        @Override
199
        public void reset() {
200
            this.value = null;
201
        }
202
        
203
        @Override
204
        public Boolean get() {
205
            if( this.value == null ) {
206
                this.calculate();
207
            }
208
            return this.value;
209
        }
210
    } 
211
    
212
    protected final JDBCHelper helper;
213

    
214
    protected CalculatedValue<Long> count = null;
215
    
216
    protected CalculatedValue<Envelope> envelope = null;
217

    
218
    protected CalculatedValue<Boolean> allowWrite = null;
219

    
220
    protected AppendOperation appendOperation = null;
221
    
222
    @SuppressWarnings({"OverridableMethodCallInConstructor", "CallToThreadStartDuringObjectConstruction"})
223
    protected JDBCStoreProviderBase(
224
            JDBCStoreParameters params,
225
            DataStoreProviderServices storeServices,
226
            DynObject metadata,
227
            JDBCHelper helper
228
    ) throws InitializeException {
229
        super(params, storeServices, metadata);
230
        this.helper = helper;
231
        this.initializeFeatureType();
232
        try {
233
            if( BooleanUtils.isTrue((Boolean) params.getDynValue("precalculateEnvelope"))  ) {
234
                FeatureType featureType = this.getStoreServices().getDefaultFeatureType();
235
                if( !StringUtils.isEmpty(featureType.getDefaultGeometryAttributeName()) ) {
236
                    Thread thread = new Thread(() -> {
237
                        LOGGER.trace("Precalculating envelope of '"+getSourceId()+"'.");
238
                        getEnvelopeValue().get();
239
                    }, "PrecalculateEnvelopeOfDBTable");
240
                    thread.start();
241
                    Thread.sleep(1);
242
                }
243
           }
244
        } catch(Exception ex) {
245
            LOGGER.warn("Probems precalculating the envelope of table '"+this.getSourceId()+"'.", ex);
246
        }
247
    }
248

    
249
    @Override
250
    public JDBCStoreParameters getParameters() {
251
        return (JDBCStoreParameters) super.getParameters();
252
    }  
253

    
254
    @Override
255
    public JDBCHelper getHelper() {
256
        return helper;
257
    }
258
    
259
    public OperationsFactory getOperations() {
260
        return this.getHelper().getOperations();
261
    }
262
    
263
    @Override
264
    public String getProviderName() {
265
        return this.getHelper().getProviderName();
266
    }
267
    
268
    @Override
269
    public int getOIDType() {
270
        return DataTypes.UNKNOWN;
271
    }
272

    
273
    @Override
274
    public Object createNewOID() {
275
        return null;
276
    }
277
    
278
    @Override
279
    public boolean allowAutomaticValues() {
280
        return this.getHelper().allowAutomaticValues();
281
    }
282

    
283
    @Override
284
    public boolean allowWrite() {
285
        return this.getAllowWriteValue().get();
286
    }
287
    
288
    @Override
289
    public Object getDynValue(String name) throws DynFieldNotFoundException {
290
        try {
291
            if (DataStore.METADATA_ENVELOPE.equalsIgnoreCase(name)) {
292
                Envelope env = this.getEnvelope();
293
                if (env != null) {
294
                    return env;
295
                }
296
            } else if (DataStore.METADATA_CRS.equalsIgnoreCase(name)) {
297
                IProjection proj;
298
                proj = this.getFeatureStore().getDefaultFeatureType().getDefaultSRS();
299
                if (proj != null) {
300
                    return proj;
301
                }
302
            }
303
        } catch (DataException e) {
304
            throw new RuntimeException(e);
305
        }
306
        return super.getDynValue(name);
307
    }
308

    
309
    @Override
310
    public CalculatedValue<Long> getCountValue() {
311
        if( this.count == null ) {
312
            this.count = new CountValue();
313
        }
314
        return this.count;
315
    }
316

    
317
    @Override
318
    public CalculatedValue<Envelope> getEnvelopeValue() {
319
        if( this.envelope == null ) {
320
            this.envelope = new EnvelopeValue();
321
        }
322
        return this.envelope;
323
    }
324
    
325
    @Override
326
    public CalculatedValue<Boolean> getAllowWriteValue() {
327
        if( this.allowWrite == null ) {
328
            this.allowWrite = new AllowWriteValue();
329
        }
330
        return this.allowWrite;
331
    }
332
    
333
    @Override
334
    public long getFeatureCount() throws DataException {
335
        return this.getCountValue().get();
336
    }
337
    
338
    @Override
339
    public boolean closeResourceRequested(ResourceProvider resource) {
340
        ResulSetControler resulSetControler = this.getHelper().getResulSetControler();
341
        resulSetControler.pack();
342
        return resulSetControler.getOpenCount() == 0;
343
    }
344

    
345
    @Override
346
    public void close() throws CloseException {
347
        JDBCUtils.closeQuietly(this.getHelper());
348
    }
349

    
350
    @Override
351
    public void resourceChanged(ResourceProvider resource) {
352
        this.getStoreServices().notifyChange(
353
                DataStoreNotification.RESOURCE_CHANGED,
354
                resource
355
        );
356
    }
357

    
358
    @Override
359
    public DataServerExplorer getExplorer() throws ReadException {
360
        DataManager manager = DALLocator.getDataManager();
361
        JDBCServerExplorerParameters exParams;
362
        JDBCStoreParameters params = getParameters();
363
        try {
364
            exParams = this.getHelper().createServerExplorerParameters();
365
            DynField[] fields = exParams.getDynClass().getDynFields();
366
            for (DynField field : fields) {
367
                try {
368
                    exParams.setDynValue(field.getName(), params.getDynValue(field.getName()));
369
                } catch(Exception ex) {
370
                    // Ignore
371
                }
372
            }
373
            exParams.setHost(params.getHost());
374
            exParams.setPort(params.getPort());
375
            exParams.setDBName(params.getDBName());
376
            exParams.setUser(params.getUser());
377
            exParams.setPassword(params.getPassword());
378
            exParams.setUrl(params.getUrl());
379
            exParams.setCatalog(params.getCatalog());
380
            exParams.setSchema(params.getSchema());
381
            exParams.setJDBCDriverClassName(params.getJDBCDriverClassName());
382

    
383
            return manager.openServerExplorer(exParams.getExplorerName(), exParams);
384
        } catch (Exception e) {
385
            throw new ReadException(this.getProviderName(), e);
386
        }
387
    }
388

    
389
    @Override
390
    protected void doDispose() throws BaseException {
391
        this.close();
392
        this.getHelper().dispose();
393
        super.doDispose();
394
    }
395

    
396
    @Override
397
    public String getSourceId() {
398
        try {
399
            return this.getHelper().getSourceId(this.getParameters());
400
        } catch(Exception ex) {
401
            return "unknow";
402
        }
403
    }
404

    
405
    @Override
406
    public String getName() {
407
        return this.getParameters().getTable();
408
    }
409

    
410
    @Override
411
    public String getFullName() {
412
        return this.getHelper().getSourceId(this.getParameters());
413
    }
414

    
415
    private static class DummyResource extends AbstractResource {
416

    
417
        private final String name;
418

    
419
        DummyResource(String name) throws InitializeException {
420
            super((ResourceParameters)null);
421
            this.name = name;
422
        }
423
        
424
        @Override
425
        public String getName() throws AccessResourceException {
426
            return MessageFormat.format("DummyResource({0})",
427
                                new Object[] { this.name });
428
        }
429

    
430
        @Override
431
        public Object get() throws AccessResourceException {
432
            return null;
433
        }
434

    
435
        @Override
436
        public boolean isThis(ResourceParameters parameters) throws ResourceException {
437
            return true;
438
        }
439
        
440
    }
441
    
442
    @Override
443
    public ResourceProvider getResource() {
444
        ResourceProvider r = getHelper().getResource();
445
        if( r == null ) {
446
            try {
447
                r = new DummyResource(this.getName());
448
            } catch (InitializeException ex) {
449
                LOGGER.warn("Can't create DummyResource",ex);
450
                // Do nothing
451
            }
452
        }
453
        return r;
454
    }
455

    
456
    @Override
457
    public void open() throws OpenException {
458

    
459
    }
460

    
461
    @Override
462
    public FeatureSetProvider createSet(
463
            FeatureQuery query,
464
            FeatureType featureType
465
        ) throws DataException {
466
        
467
        FeatureSetProvider set = new JDBCSetProvider(
468
                this,
469
                this.getHelper(), 
470
                query, 
471
                featureType
472
        );
473
        
474
        return set;
475
    }
476

    
477
    protected void initializeFeatureType() {
478
        EditableFeatureType type = this.getStoreServices().createFeatureType(getName());
479
        JDBCStoreParameters params = this.getParameters();
480
        List<String> primaryKeys = null;
481
        if( params.getPkFields() != null ) {
482
            primaryKeys = Arrays.asList(params.getPkFields());
483
        }
484
        FetchFeatureTypeOperation fetchFeatureType = 
485
             this.getOperations().createFetchFeatureType(
486
                type,
487
                this.getOperations().createTableReference(params),
488
                primaryKeys,
489
                params.getDefaultGeometryField(),
490
                params.getCRS()
491
            );
492
        fetchFeatureType.perform();
493

    
494
        if (!StringUtils.isBlank(params.getDefaultGeometryField())) {
495
            if (!params.getDefaultGeometryField().equalsIgnoreCase(type.getDefaultGeometryAttributeName())) {
496
                if (type.getAttributeDescriptor(params.getDefaultGeometryField()) != null) {
497
                    type.setDefaultGeometryAttributeName(params.getDefaultGeometryField());
498
                } else {
499
                    type.setDefaultGeometryAttributeName(null);
500
                }
501
                if (type.getDefaultGeometryAttribute() != null) {
502
                    EditableFeatureAttributeDescriptor attr = (EditableFeatureAttributeDescriptor) type.getDefaultGeometryAttribute();
503
                    attr.setGeometryType(Geometry.TYPES.GEOMETRY, Geometry.SUBTYPES.GEOM2D);
504
                }
505
            } else {
506
                type.setDefaultGeometryAttributeName(null);
507
            }
508
        }
509
        
510
        FeatureType defaultType = type.getNotEditableCopy();
511
        this.getHelper().setProviderFeatureType(defaultType);
512
        List<FeatureType> types = Collections.singletonList(defaultType);
513
        this.getStoreServices().setFeatureTypes(types, defaultType);
514
    }
515
    
516
    @Override
517
    protected FeatureProvider internalGetFeatureProviderByReference(
518
            FeatureReferenceProviderServices reference, 
519
            FeatureType featureType
520
        ) throws DataException {
521
        JDBCStoreParameters params = this.getParameters();
522
        FetchFeatureProviderByReferenceOperation fetchFeatureProviderByReference = 
523
            this.getOperations().createFetchFeatureProviderByReference(
524
                reference, 
525
                featureType, 
526
                this.getOperations().createTableReference(params)
527
            );
528
        FeatureProvider feature = (FeatureProvider) fetchFeatureProviderByReference.perform();
529
        return feature;
530
    }
531
    
532
    @Override
533
    public Envelope getEnvelope() throws DataException {
534
        return this.getEnvelopeValue().get();
535
    }
536

    
537
    @Override
538
    public void performChanges(Iterator deleteds, Iterator inserteds,
539
                    Iterator updateds, Iterator featureTypesChanged)
540
                    throws DataException {
541

    
542
        FeatureType type = this.getFeatureStore().getDefaultFeatureType();
543
        JDBCStoreParameters params = this.getParameters();
544
        PerformChangesOperation performChanges = this.getOperations().createPerformChanges(
545
                this.getOperations().createTableReference(params),
546
                type, 
547
                deleteds, 
548
                inserteds, 
549
                updateds, 
550
                featureTypesChanged
551
        );
552
        performChanges.perform();
553
        if( performChanges.isTypeChanged() ) {
554
            // Get rules before initializing feature type
555
            FeatureRules saved_rules = getFeatureStore().getDefaultFeatureType().getRules();
556

    
557
             // This initialization loses the feature type rules
558
            this.initializeFeatureType();
559

    
560
            // Get new feature type, clear rules and add the ones saved previously
561
            FeatureType featureType = getFeatureStore().getDefaultFeatureType();
562
            FeatureRules rules = featureType.getRules();
563
            rules.clear();
564
            for (FeatureRule rule : saved_rules) {
565
                rules.add(rule);
566
            }
567
        }
568
        this.getCountValue().reset();
569
        this.getEnvelopeValue().reset();
570
    }    
571
    
572
    @Override
573
    public boolean supportsAppendMode() {
574
        return true;
575
    }
576

    
577
    protected AppendOperation getAppendOperation() throws DataException {
578
        if( this.appendOperation == null ) {
579
            FeatureType type = this.getFeatureStore().getDefaultFeatureType();
580
            JDBCStoreParameters params = this.getParameters();
581
            this.appendOperation = this.getOperations().createAppend(
582
                this.getOperations().createTableReference(params),
583
                type 
584
            );
585
        }
586
        return this.appendOperation;
587
    }
588
    
589
    @Override
590
    public void endAppend() throws DataException {
591
        this.getAppendOperation().end();
592
    }
593

    
594
    @Override
595
    public void abortAppend() throws DataException {
596
        this.getAppendOperation().abort();
597
    }
598
    
599
    @Override
600
    public void beginAppend() throws DataException {
601
        this.getAppendOperation().begin();
602
    }
603

    
604
    @Override
605
    public void append(final FeatureProvider featureProvider) throws DataException {
606
        this.getAppendOperation().append(featureProvider);
607
    }    
608
    
609
    @Override
610
    public boolean canWriteGeometry(int geometryType, int geometrySubtype)
611
            throws DataException {
612
        return this.getHelper().canWriteGeometry(geometryType,geometrySubtype);
613
    }
614

    
615
    @Override
616
    public boolean supportsPassThroughMode() {
617
        return true;
618
    }
619

    
620
    @Override
621
    public void passThroughInsert(FeatureProvider featureProvider) throws DataException {
622
        FeatureType type = this.getFeatureStore().getDefaultFeatureType();
623
        JDBCStoreParameters params = this.getParameters();
624
        PerformChangesOperation performChanges = this.getOperations().createPerformChanges(
625
                this.getOperations().createTableReference(params),
626
                type, 
627
                Collections.emptyIterator(), 
628
                Collections.singletonList(featureProvider).iterator(),
629
                Collections.emptyIterator(), 
630
                Collections.emptyIterator()
631
        );
632
        performChanges.perform();
633
    }
634

    
635
    @Override
636
    public void passThroughUpdate(FeatureProvider featureProvider) throws DataException {
637
        FeatureType type = this.getFeatureStore().getDefaultFeatureType();
638
        JDBCStoreParameters params = this.getParameters();
639
        PerformChangesOperation performChanges = this.getOperations().createPerformChanges(
640
                this.getOperations().createTableReference(params),
641
                type, 
642
                Collections.emptyIterator(), 
643
                Collections.emptyIterator(), 
644
                Collections.singletonList(featureProvider).iterator(),
645
                Collections.emptyIterator()
646
        );
647
        performChanges.perform();
648
    }
649
    
650
    public void passThroughUpdate(Object[] parameters, Expression filter){
651
        //TODO: 
652
    }
653

    
654
    @Override
655
    public void passThroughDelete(FeatureReferenceProviderServices featureReference) throws DataException {
656
        FeatureType type = this.getFeatureStore().getDefaultFeatureType();
657
        JDBCStoreParameters params = this.getParameters();
658
        PerformChangesOperation performChanges = this.getOperations().createPerformChanges(
659
                this.getOperations().createTableReference(params),
660
                type, 
661
                Collections.singletonList(featureReference).iterator(),
662
                Collections.emptyIterator(), 
663
                Collections.emptyIterator(), 
664
                Collections.emptyIterator()
665
        );
666
        performChanges.perform();
667
    }
668
    
669
    public void passThroughDelete(Expression expression) throws DataException {
670
        //TODO:
671
    }
672
}